/* 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 fedora.server.rest; import java.io.ByteArrayInputStream; import java.io.CharArrayWriter; import java.io.InputStream; import java.net.URI; import java.net.URLEncoder; import java.util.Date; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.log4j.Logger; import fedora.common.Constants; import fedora.server.Context; import fedora.server.access.ObjectProfile; import fedora.server.rest.RestUtil.RequestContent; import fedora.server.utilities.StreamUtility; /** * Implement /objects/pid/* REST API * * @author cuong.tran@yourmediashelf.com * @version $Id$ */ @Path("/{pid}") public class FedoraObjectResource extends BaseRestResource { private final String FOXML1_1 = "info:fedora/fedora-system:FOXML-1.1"; private static Logger LOG = Logger.getLogger(FedoraObjectResource.class.getName()); /** * Exports the entire digital object in the specified XML format * ("info:fedora/fedora-system:FOXML-1.1" or * "info:fedora/fedora-system:METSFedoraExt-1.1"), and encoded appropriately * for the specified export context ("public", "migrate", or "archive"). * * GET /objects/{pid}/export ? format context encoding */ @Path("/export") @GET @Produces(XML) public Response getObjectExport( @PathParam(RestParam.PID) String pid, @QueryParam(RestParam.FORMAT) @DefaultValue(FOXML1_1) String format, @QueryParam(RestParam.EXPORT_CONTEXT) String exportContext, @QueryParam(RestParam.ENCODING) @DefaultValue(DEFAULT_ENC) String encoding) { try { Context context = getContext(); InputStream is = apiMService.export(context, pid, format, exportContext, encoding); return Response.ok(is, TEXT_XML).build(); } catch (Exception ex) { return handleException(ex); } } /** * Gets a list of timestamps indicating when components changed in an * object. This is a set of timestamps indicating when a datastream or * disseminator was created or modified in the object. These timestamps can * be used to request a timestamped dissemination request to view the object * as it appeared at a specific point in time. * * GET /objects/{pid}/versions ? format */ @Path("/versions") @GET public Response getObjectHistory( @PathParam(RestParam.PID) String pid, @QueryParam(RestParam.FORMAT) @DefaultValue(HTML) String format) { try { Context context = getContext(); String[] objectHistory = apiAService.getObjectHistory(context, pid); String xml = getSerializer(context).objectHistoryToXml(objectHistory, pid); MediaType mime = RestHelper.getContentType(format); if (TEXT_HTML.isCompatible(mime)) { CharArrayWriter writer = new CharArrayWriter(); transform(xml, "access/viewObjectHistory.xslt", writer); xml = writer.toString(); } return Response.ok(xml, mime).build(); } catch (Exception ex) { return handleException(ex); } } /** * Gets a profile of the object which includes key metadata fields and URLs * for the object Dissemination Index and the object Item Index. Can be * thought of as a default view of the object. * * GET /objects/{pid}/objectXML */ @Path("/objectXML") @GET @Produces(XML) public Response getObjectXML( @PathParam(RestParam.PID) String pid) { try { Context context = getContext(); InputStream is = apiMService.getObjectXML(context, pid, DEFAULT_ENC); return Response.ok(is, TEXT_XML).build(); } catch (Exception ex) { return handleException(ex); } } /** * Gets a profile of the object which includes key metadata fields and URLs * for the object Dissemination Index and the object Item Index. Can be * thought of as a default view of the object. * * GET /objects/{pid} ? format asOfDateTime */ @GET @Produces( { HTML, XML }) public Response getObjectProfile( @PathParam(RestParam.PID) String pid, @QueryParam(RestParam.AS_OF_DATE_TIME) String dateTime, @QueryParam(RestParam.FORMAT) @DefaultValue(HTML) String format) { try { Date asOfDateTime = parseDate(dateTime); Context context = getContext(); ObjectProfile objProfile = apiAService.getObjectProfile(context, pid, asOfDateTime); String xml = getSerializer(context).objectProfileToXML(objProfile, asOfDateTime); MediaType mime = RestHelper.getContentType(format); if (TEXT_HTML.isCompatible(mime)) { CharArrayWriter writer = new CharArrayWriter(); transform(xml, "access/viewObjectProfile.xslt", writer); xml = writer.toString(); } return Response.ok(xml, mime).build(); } catch (Exception ex) { return handleException(ex); } } /** * Permanently removes an object from the repository. * * DELETE /objects/{pid} ? logMessage force */ @DELETE public Response deleteObject( @PathParam(RestParam.PID) String pid, @QueryParam("logMessage") String logMessage, @QueryParam("force") @DefaultValue("false") boolean force) { try { Context context = getContext(); apiMService.purgeObject(context, pid, logMessage, force); return Response.noContent().build(); } catch (Exception ex) { return handleException(ex); } } /** * Create/Update a new digital object. If no xml given in the body, will * create an empty object. * * POST /objects/{pid} ? label logMessage format encoding namespace ownerId state */ @POST @Consumes({ XML, FORM }) public Response createObject( @javax.ws.rs.core.Context HttpHeaders headers, @PathParam(RestParam.PID) String pid, @QueryParam(RestParam.LABEL) String label, @QueryParam(RestParam.LOG_MESSAGE) String logMessage, @QueryParam(RestParam.FORMAT) @DefaultValue(FOXML1_1) String format, @QueryParam(RestParam.ENCODING) @DefaultValue(DEFAULT_ENC) String encoding, @QueryParam(RestParam.NAMESPACE) String namespace, @QueryParam(RestParam.OWNER_ID) String ownerID, @QueryParam(RestParam.STATE) @DefaultValue("A") String state, @QueryParam(RestParam.IGNORE_MIME) @DefaultValue("false") boolean ignoreMime) { try { Context context = getContext(); InputStream is = null; boolean newPID = false; // Determine if content is provided RestUtil restUtil = new RestUtil(); RequestContent content = restUtil.getRequestContent(servletRequest, headers); if(content != null && content.getContentStream() != null) { if(ignoreMime) { is = content.getContentStream(); } else { // Make sure content is XML String contentMime = content.getMimeType(); if(contentMime != null && TEXT_XML.isCompatible(MediaType.valueOf(contentMime))) { is = content.getContentStream(); } } } // If no content is provided, use a FOXML template if (is == null) { if (pid == null || pid.equals("new")) { pid = apiMService.getNextPID(context, 1, namespace)[0]; } ownerID = context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); is = new ByteArrayInputStream(getFOXMLTemplate(pid, label, ownerID, encoding).getBytes()); } else { // content provided, but no pid if (pid == null || pid.equals("new")) { newPID = true; } if(namespace != null && !namespace.equals("")) { LOG.warn("The namespace parameter is only applicable when object " + "content is not provided, thus the namespace provided '" + namespace + "' has been ignored."); } } pid = apiMService.ingest(context, is, logMessage, format, encoding, newPID); URI createdLocation = uriInfo.getRequestUri().resolve(URLEncoder.encode(pid, DEFAULT_ENC)); return Response.created(createdLocation).entity(pid).build(); } catch (Exception ex) { return handleException(ex); } } /** * Update digital object * * PUT /objects/{pid} ? label logMessage ownerId state * * @see API-M.modifyObject */ @PUT public Response updateObject( @PathParam(RestParam.PID) String pid, @QueryParam(RestParam.LABEL) String label, @QueryParam(RestParam.LOG_MESSAGE) String logMessage, @QueryParam(RestParam.OWNER_ID) String ownerID, @QueryParam(RestParam.STATE) @DefaultValue("A") String state) { try { Context context = getContext(); apiMService.modifyObject(context, pid, state, label, ownerID, logMessage); return Response.ok().build(); } catch (Exception ex) { return handleException(ex); } } private static String getFOXMLTemplate( String pid, String label, String ownerId, String encoding) { StringBuilder xml = new StringBuilder(); xml.append("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n"); xml.append("<foxml:digitalObject VERSION=\"1.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); xml.append(" xmlns:foxml=\"info:fedora/fedora-system:def/foxml#\"\n"); xml.append(" xsi:schemaLocation=\"" + Constants.FOXML.uri + " " + Constants.FOXML1_1.xsdLocation + "\""); if (pid != null && pid.length() > 0) { xml.append("\n PID=\"" + StreamUtility.enc(pid) + "\">\n"); } else { xml.append(">\n"); } xml.append(" <foxml:objectProperties>\n"); xml.append(" <foxml:property NAME=\"info:fedora/fedora-system:def/model#state\" VALUE=\"A\"/>\n"); xml.append(" <foxml:property NAME=\"info:fedora/fedora-system:def/model#label\" VALUE=\"" + StreamUtility.enc(label) + "\"/>\n"); xml.append(" <foxml:property NAME=\"info:fedora/fedora-system:def/model#ownerId\" VALUE=\"" + ownerId + "\"/>\n"); xml.append(" </foxml:objectProperties>\n"); xml.append("</foxml:digitalObject>"); return xml.toString(); } }