/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.rest;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.codehaus.jackson.map.ObjectMapper;
import org.fcrepo.common.Constants;
import org.fcrepo.server.Context;
import org.fcrepo.server.ReadOnlyContext;
import org.fcrepo.server.Server;
import org.fcrepo.server.access.Access;
import org.fcrepo.server.errors.ObjectValidityException;
import org.fcrepo.server.errors.RangeNotSatisfiableException;
import org.fcrepo.server.errors.ResourceLockedError;
import org.fcrepo.server.errors.ResourceNotFoundError;
import org.fcrepo.server.errors.authorization.AuthzException;
import org.fcrepo.server.management.Management;
import org.fcrepo.server.storage.DOManager;
import org.fcrepo.server.storage.types.MIMETypedStream;
import org.fcrepo.server.storage.types.Property;
import org.fcrepo.utilities.XmlTransformUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A barebone RESTFUL resource implementation.
*
* @author cuong.tran@yourmediashelf.com
* @version $Id$
*/
public class BaseRestResource {
public static final String VALID_PID_PART = "/{pid : ([A-Za-z0-9]|-|\\.)+(:|%3A|%3a)(([A-Za-z0-9])|-|\\.|~|_|(%[0-9A-F]{2}))+}";
private static final Logger LOGGER = LoggerFactory.getLogger(BaseRestResource.class);
static final String[] EMPTY_STRING_ARRAY = new String[0];
static final String DEFAULT_ENC = "UTF-8";
public static final String FORM = "multipart/form-data";
public static final String HTML = "text/html";
public static final String XML = "text/xml";
public static final String ZIP = "application/zip";
public static final MediaType TEXT_HTML = new MediaType("text", "html");
public static final MediaType TEXT_XML = new MediaType("text", "xml");
public static final MediaType APP_ZIP = new MediaType("application", "zip");
protected Server m_server;
protected Management m_management;
protected Access m_access;
protected String m_hostname;
protected ObjectMapper m_mapper;
protected DatastreamFilenameHelper m_datastreamFilenameHelper;
@javax.ws.rs.core.Context
protected HttpServletRequest m_servletRequest;
@javax.ws.rs.core.Context
protected UriInfo m_uriInfo;
@javax.ws.rs.core.Context
protected javax.ws.rs.core.HttpHeaders m_headers;
public BaseRestResource(Server server) {
try {
this.m_server = server;
this.m_management = (Management) m_server.getModule("org.fcrepo.server.management.Management");
this.m_access = (Access) m_server.getModule("org.fcrepo.server.access.Access");
this.m_hostname = m_server.getParameter("fedoraServerHost");
m_datastreamFilenameHelper = new DatastreamFilenameHelper(m_server,
(DOManager) m_server.getModule("org.fcrepo.server.storage.DOManager"));
m_mapper = new ObjectMapper();
} catch (Exception ex) {
throw new RestException("Unable to locate Fedora server instance", ex);
}
}
protected Context getContext() {
return ReadOnlyContext.getContext(Constants.HTTP_REQUEST.REST.uri,
m_servletRequest);
}
protected DefaultSerializer getSerializer(Context context) {
return new DefaultSerializer(m_hostname, context);
}
protected void transform(String xml, String xslt, Writer out)
throws TransformerFactoryConfigurationError,
TransformerConfigurationException,
TransformerException {
transform(new StringReader(xml), xslt, out);
}
protected void transform(Reader xml, String xslt, Writer out)
throws TransformerFactoryConfigurationError,
TransformerConfigurationException,
TransformerException {
File xslFile = new File(m_server.getHomeDir(), xslt);
// XmlTransformUtility maintains a cache of Templates
Templates template =
XmlTransformUtility.getTemplates(xslFile);
Transformer transformer = template.newTransformer();
String appContext = getContext().getEnvironmentValue(Constants.FEDORA_APP_CONTEXT_NAME);
transformer.setParameter("fedora", appContext);
transformer.transform(new StreamSource(xml), new StreamResult(out));
}
protected Response buildResponse(MIMETypedStream result) throws Exception {
ResponseBuilder builder = null;
switch (result.getStatusCode()) {
case HttpStatus.SC_MOVED_TEMPORARILY:
URI location = URI.create(IOUtils.toString(result.getStream()));
return Response.temporaryRedirect(location).build();
case HttpStatus.SC_NOT_MODIFIED:
builder = Response.notModified();
break;
default:
builder = Response.status(result.getStatusCode());
if (result.getSize() != -1L){
builder.header(HttpHeaders.CONTENT_LENGTH,result.getSize());
}
if (!result.getMIMEType().isEmpty()){
builder.type(result.getMIMEType());
}
InputStream content = result.getStream();
if (content != null) {
builder.entity(result.getStream());
}
}
if (result.header != null) {
for (Property header : result.header) {
if (header.name != null
&& !(header.name.equalsIgnoreCase(HttpHeaders.TRANSFER_ENCODING))
&& !(header.name.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH))
&& !(header.name.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE))) {
builder.header(header.name, header.value);
}
}
}
return builder.build();
}
private Response handleException(Exception ex) {
if (ex instanceof ResourceNotFoundError) {
LOGGER.warn("Resource not found: " + ex.getMessage() + "; unable to fulfill REST API request", ex);
return Response.status(Status.NOT_FOUND).entity(ex.getMessage()).type("text/plain").build();
} else if (ex instanceof AuthzException) {
LOGGER.warn("Authorization failed; unable to fulfill REST API request", ex);
return Response.status(Status.UNAUTHORIZED).entity(ex.getMessage()).type("text/plain").build();
} else if (ex instanceof IllegalArgumentException) {
LOGGER.warn("Bad request; unable to fulfill REST API request", ex);
return Response.status(Status.BAD_REQUEST).entity(ex.getMessage()).type("text/plain").build();
} else if (ex instanceof ResourceLockedError) {
LOGGER.warn("Lock exception; unable to fulfill REST API request", ex);
return Response.status(Status.CONFLICT).entity(ex.getMessage()).type(MediaType.TEXT_PLAIN).build();
} else if (ex instanceof ObjectValidityException){
LOGGER.warn("Validation exception; unable to fulfill REST API request", ex);
if (((ObjectValidityException) ex).getValidation() != null) {
String errors = DefaultSerializer.objectValidationToXml(((ObjectValidityException) ex).getValidation());
return Response.status(Status.BAD_REQUEST).entity(errors).type(MediaType.TEXT_XML).build();
} else {
return Response.status(Status.BAD_REQUEST).entity(ex.getMessage()).type(MediaType.TEXT_PLAIN).build();
}
} else if (ex instanceof RangeNotSatisfiableException) {
LOGGER.warn("Bad range request: " + ex.getMessage() + "; unable to fulfill REST API request", ex);
return Response.status(RangeNotSatisfiableException.STATUS_CODE).entity(ex.getMessage()).type(MediaType.TEXT_PLAIN).build();
} else {
LOGGER.error("Unexpected error fulfilling REST API request", ex);
throw new WebApplicationException(ex);
}
}
protected Response handleException(Exception ex, boolean flash) {
Response error = handleException(ex);
if (flash) error = Response.ok(error.getEntity()).build();
return error;
}
}