blob: 55214b98e33010bcb4b3d66b39ef7cddf83396e4 [file] [log] [blame]
/**
* Copyright (c) 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
*/
package org.eclipse.emf.ecore.resource.impl;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ContentHandler;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.URIHandler;
/**
* An implementation of a {@link URIHandler URI handler}.
*
*/
public class URIHandlerImpl implements URIHandler
{
/**
* Creates an instance.
*/
public URIHandlerImpl()
{
super();
}
/**
* This implementation always returns true; clients are generally expected to override this.
*/
public boolean canHandle(URI uri)
{
return true;
}
/**
* Returns the value of the {@link URIConverter#OPTION_URI_CONVERTER URI converter option}.
* @param options the options in which to look for the URI converter.
* @return the value of the URI converter option.
*/
protected URIConverter getURIConverter(Map<?, ?> options)
{
return (URIConverter)options.get(URIConverter.OPTION_URI_CONVERTER);
}
/**
* Returns the value of the {@link URIConverter#OPTION_RESPONSE response option}.
* @param options the options in which to look for the response option.
* @return the value of the response option.
*/
@SuppressWarnings("unchecked")
protected Map<Object, Object> getResponse(Map<?, ?> options)
{
return (Map<Object, Object>)options.get(URIConverter.OPTION_RESPONSE);
}
/**
* Returns the value of the {@link URIConverter#OPTION_REQUESTED_ATTRIBUTES requested attributes option}.
* @param options the options in which to look for the requested attributes option.
* @return the value of the requested attributes option.
*/
@SuppressWarnings("unchecked")
protected Set<String> getRequestedAttributes(Map<?, ?> options)
{
return (Set<String>)options.get(URIConverter.OPTION_REQUESTED_ATTRIBUTES);
}
/**
* Returns the value of the {@link URIConverter#OPTION_TIMEOUT timeout option}.
* @param options the options in which to look for the timeout option.
* @return the value of the timeout option, or <code>0</code> if not present.
* @since 2.9
*/
protected int getTimeout(Map<?, ?> options)
{
Integer timeout = (Integer)options.get(URIConverter.OPTION_TIMEOUT);
return timeout == null ? 0 : timeout.intValue();
}
/**
* Creates an output stream for the URI, assuming it's a URL, and returns it.
* Specialized support is provided for HTTP URLs.
* @return an open output stream.
* @exception IOException if there is a problem obtaining an open output stream.
*/
public OutputStream createOutputStream(URI uri, Map<?, ?> options) throws IOException
{
try
{
URL url = new URL(uri.toString());
final URLConnection urlConnection = url.openConnection();
urlConnection.setDoOutput(true);
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
}
if (urlConnection instanceof HttpURLConnection)
{
final HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
httpURLConnection.setRequestMethod("PUT");
return
new FilterOutputStream(urlConnection.getOutputStream())
{
@Override
public void close() throws IOException
{
super.close();
int responseCode = httpURLConnection.getResponseCode();
switch (responseCode)
{
case HttpURLConnection.HTTP_OK:
case HttpURLConnection.HTTP_CREATED:
case HttpURLConnection.HTTP_NO_CONTENT:
{
break;
}
default:
{
throw new IOException("PUT failed with HTTP response code " + responseCode);
}
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
out.write(b, off, len);
}
};
}
else
{
OutputStream result = urlConnection.getOutputStream();
final Map<Object, Object> response = getResponse(options);
if (response != null)
{
result =
new FilterOutputStream(result)
{
@Override
public void close() throws IOException
{
try
{
super.close();
}
finally
{
response.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, urlConnection.getLastModified());
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
out.write(b, off, len);
}
};
}
return result;
}
}
catch (RuntimeException exception)
{
throw new Resource.IOWrappedException(exception);
}
}
/**
* Creates an input stream for the URI, assuming it's a URL, and returns it.
* @return an open input stream.
* @exception IOException if there is a problem obtaining an open input stream.
*/
public InputStream createInputStream(URI uri, Map<?, ?> options) throws IOException
{
try
{
URL url = new URL(uri.toString());
final URLConnection urlConnection = url.openConnection();
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
}
InputStream result = urlConnection.getInputStream();
Map<Object, Object> response = getResponse(options);
if (response != null)
{
response.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, urlConnection.getLastModified());
}
return result;
}
catch (RuntimeException exception)
{
throw new Resource.IOWrappedException(exception);
}
}
/**
* Only HTTP connections support delete.
*/
public void delete(URI uri, Map<?, ?> options) throws IOException
{
try
{
URL url = new URL(uri.toString());
URLConnection urlConnection = url.openConnection();
urlConnection.setDoOutput(true);
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
}
if (urlConnection instanceof HttpURLConnection)
{
final HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
httpURLConnection.setRequestMethod("DELETE");
int responseCode = httpURLConnection.getResponseCode();
switch (responseCode)
{
case HttpURLConnection.HTTP_OK:
case HttpURLConnection.HTTP_ACCEPTED:
case HttpURLConnection.HTTP_NO_CONTENT:
{
break;
}
default:
{
throw new IOException("DELETE failed with HTTP response code " + responseCode);
}
}
}
else
{
throw new IOException("Delete is not supported for " + uri);
}
}
catch (RuntimeException exception)
{
throw new Resource.IOWrappedException(exception);
}
}
/**
* This implementation delegates to the {@link #getURIConverter(Map) URI converter}'s {@link URIConverter#getContentHandlers() content handlers}.
*/
public Map<String, ?> contentDescription(URI uri, Map<?, ?> options) throws IOException
{
URIConverter uriConverter = (URIConverter)options.get(URIConverter.OPTION_URI_CONVERTER);
InputStream inputStream = null;
Map<String, ?> result = null;
Map<Object, Object> context = new HashMap<Object, Object>();
try
{
for (ContentHandler contentHandler : uriConverter.getContentHandlers())
{
if (contentHandler.canHandle(uri))
{
if (inputStream == null)
{
try
{
inputStream = createInputStream(uri, options);
}
catch (IOException exception)
{
inputStream = new ByteArrayInputStream(new byte [0]);
}
if (!inputStream.markSupported())
{
inputStream = new BufferedInputStream(inputStream);
}
inputStream.mark(Integer.MAX_VALUE);
}
else
{
inputStream.reset();
}
Map<String, ?> contentDescription = contentHandler.contentDescription(uri, inputStream, options, context);
switch ((ContentHandler.Validity)contentDescription.get(ContentHandler.VALIDITY_PROPERTY))
{
case VALID:
{
return contentDescription;
}
case INDETERMINATE:
{
if (result == null)
{
result = contentDescription;
}
break;
}
case INVALID:
{
break;
}
}
}
}
}
finally
{
if (inputStream != null)
{
inputStream.close();
}
}
return result == null ? ContentHandler.INVALID_CONTENT_DESCRIPTION : result;
}
/**
* If a stream can be created the file exists.
* Specialized support is provided for HTTP connections to avoid fetching the whole stream in that case.
*/
public boolean exists(URI uri, Map<?, ?> options)
{
try
{
URL url = new URL(uri.toString());
URLConnection urlConnection = url.openConnection();
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
}
if (urlConnection instanceof HttpURLConnection)
{
HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
httpURLConnection.setRequestMethod("HEAD");
int responseCode = httpURLConnection.getResponseCode();
// TODO
// I'm concerned that folders will often return 401 or even 403.
// So should we consider something to exist even though access if unauthorized or forbidden?
//
return responseCode == HttpURLConnection.HTTP_OK;
}
else
{
InputStream inputStream = urlConnection.getInputStream();
inputStream.close();
return true;
}
}
catch (Throwable exception)
{
return false;
}
}
public Map<String, ?> getAttributes(URI uri, Map<?, ?> options)
{
Map<String, Object> result = new HashMap<String, Object>();
Set<String> requestedAttributes = getRequestedAttributes(options);
try
{
URL url = new URL(uri.toString());
URLConnection urlConnection = null;
if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_READ_ONLY))
{
urlConnection = url.openConnection();
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
}
if (urlConnection instanceof HttpURLConnection)
{
HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
httpURLConnection.setRequestMethod("OPTIONS");
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK)
{
String allow = httpURLConnection.getHeaderField("Allow");
result.put(URIConverter.ATTRIBUTE_READ_ONLY, allow == null || !allow.contains("PUT"));
}
urlConnection = null;
}
else
{
result.put(URIConverter.ATTRIBUTE_READ_ONLY, true);
}
}
if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_TIME_STAMP))
{
if (urlConnection == null)
{
urlConnection = url.openConnection();
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
}
if (urlConnection instanceof HttpURLConnection)
{
HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
httpURLConnection.setRequestMethod("HEAD");
httpURLConnection.getResponseCode();
}
}
if (urlConnection.getHeaderField("last-modified") != null)
{
result.put(URIConverter.ATTRIBUTE_TIME_STAMP, urlConnection.getLastModified());
}
}
if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_LENGTH))
{
if (urlConnection == null)
{
urlConnection = url.openConnection();
int timeout = getTimeout(options);
if (timeout != 0)
{
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
}
if (urlConnection instanceof HttpURLConnection)
{
HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection;
httpURLConnection.setRequestMethod("HEAD");
httpURLConnection.getResponseCode();
}
}
if (urlConnection.getHeaderField("content-length") != null)
{
result.put(URIConverter.ATTRIBUTE_LENGTH, urlConnection.getContentLength());
}
}
}
catch (IOException exception)
{
// Ignore exceptions.
}
return result;
}
public void setAttributes(URI uri, Map<String, ?> attributes, Map<?, ?> options) throws IOException
{
// We can't update any properties via just a URL connection.
}
}