Java tutorial
/** * This document is a part of the source code and related artifacts * for CollectionSpace, an open source collections management system * for museums and related institutions: * * http://www.collectionspace.org * http://wiki.collectionspace.org * * Copyright 2009 Regents of the University of California * * Licensed under the Educational Community License (ECL), Version 2.0. * You may not use this file except in compliance with this License. * * You may obtain a copy of the ECL 2.0 License at * https://source.collectionspace.org/collection-space/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.collectionspace.services.id; import java.util.Map; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.POST; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.UriBuilder; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl; // May at some point instead use // org.jboss.resteasy.spi.NotFoundException import org.collectionspace.services.common.XmlTools; import org.collectionspace.services.common.document.BadRequestException; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.context.MultipartServiceContextFactory; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.context.ServiceContextFactory; import org.collectionspace.services.client.IdClient; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Namespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * IDResource * * Resource class to handle requests to the ID Service. * * $LastChangedRevision$ * $LastChangedDate$ */ // Set the base path component for URLs that access this service. @Path("/idgenerators") @Produces(MediaType.TEXT_PLAIN) public class IDResource extends AbstractCollectionSpaceResourceImpl<PoxPayloadIn, PoxPayloadOut> { final Logger logger = LoggerFactory.getLogger(IDResource.class); final static IDService service = new IDServiceJdbcImpl(); // Query parameter names and values. final static String QUERY_PARAM_LIST_FORMAT = "format"; final static String LIST_FORMAT_SUMMARY = "summary"; final static String LIST_FORMAT_FULL = "full"; final static String QUERY_PARAM_ID_GENERATOR_ROLE = "role"; // XML namespace for the ID Service. final static String ID_SERVICE_NAMESPACE = "http://collectionspace.org/services/id"; final static String ID_SERVICE_NAMESPACE_PREFIX = "ns2"; // Names of elements for ID generator instances, lists and list items. final static String ID_GENERATOR_NAME = "idgenerator"; final static String ID_GENERATOR_COMPONENTS_NAME = "idgenerator-components"; final static String ID_GENERATOR_LIST_NAME = "idgenerator-list"; final static String ID_GENERATOR_LIST_ITEM_NAME = "idgenerator-list-item"; // Base URL path for REST-based requests to the ID Service. // // @TODO Investigate whether this can be obtained from the // value used in the class-level @PATH annotation, above. final static String BASE_URL_PATH = "/idgenerators"; ////////////////////////////////////////////////////////////////////// /** * Constructor (no argument). */ public IDResource() { // do nothing } ////////////////////////////////////////////////////////////////////// /** * Generates and returns a new ID, from the specified ID generator. * * @param csid An identifier for an ID generator. * * @return A new ID created ("generated") by the specified ID generator. */ @POST @Path("/{csid}/ids") public Response newID(@PathParam("csid") String csid) { // @TODO The JavaDoc description reflects an as-yet-to-be-carried out // refactoring, in which the highest object type in the ID service // is that of an IDGenerator, some or all of which may be composed // of IDParts. Some IDGenerators generate IDs based on patterns, // which may be composed in part of incrementing numeric or alphabetic // components, while others may not (e.g. UUIDs, web services-based // responses). // To uniquely identify ID generators in production, we'll need to handle // both CollectionSpace IDs (csids) - a form of UUIDs/GUIDs - and some // other form of identifier to be determined, such as URLs or URNs. // @TODO We're currently returning IDs in plain text. Identify whether // there is a requirement to return an XML representation, and/or any // other representations. ResponseBuilder builder = Response.ok(); Response response = builder.build(); String newId = ""; try { ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(); // Obtain a new ID from the specified ID generator instance. newId = service.createID(ctx, csid); // If the new ID is empty, return an error response. if (newId == null || newId.trim().isEmpty()) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("ID Service returned null or empty ID").type(MediaType.TEXT_PLAIN).build(); return response; } // Build the response, setting the: // - HTTP Status code (to '201 Created') // - Content-type header (to the relevant media type) // - Entity body (to the new ID) response = Response.status(Response.Status.CREATED).entity(newId).type(MediaType.TEXT_PLAIN).build(); // @TODO Return an XML-based error results format with the // responses below. // @TODO An IllegalStateException often indicates an overflow // of an IDPart. Consider whether returning a 400 Bad Request // status code is still warranted, or whether returning some other // status would be more appropriate. } catch (DocumentNotFoundException dnfe) { response = Response.status(Response.Status.NOT_FOUND).entity(dnfe.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } catch (BadRequestException bre) { response = Response.status(Response.Status.BAD_REQUEST).entity(bre.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } catch (IllegalStateException ise) { response = Response.status(Response.Status.BAD_REQUEST).entity(ise.getMessage()) .type(MediaType.TEXT_PLAIN).build(); // This is guard code that should never be reached. } catch (Exception e) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } return response; } ////////////////////////////////////////////////////////////////////// /** * Creates a new ID generator instance. * */ @POST @Path("") @Consumes(MediaType.APPLICATION_XML) public Response createIDGenerator(String xmlPayload) { ResponseBuilder builder = Response.ok(); Response response = builder.build(); try { ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(); String csid = UUID.randomUUID().toString(); service.createIDGenerator(ctx, csid, xmlPayload); // Build the URI to be returned in the Location header. // // Gets the base URL path to this resource. UriBuilder path = UriBuilder.fromResource(IDResource.class); // @TODO Look into whether we can create the path using the // URI template in the @Path annotation to this method, rather // than the hard-coded analog to that template currently used. path.path("" + csid); // Build the response, setting the: // - HTTP Status code (to '201 Created') // - Content-type header (to the relevant media type) // - Entity body (to the new ID) response = Response.created(path.build()).entity("").type(MediaType.TEXT_PLAIN).build(); } catch (Exception e) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) .type(MediaType.TEXT_PLAIN).build(); if (logger.isDebugEnabled() == true) { e.printStackTrace(); } } return response; } ////////////////////////////////////////////////////////////////////// /** * Returns a representation of a single ID generator instance resource. * * @param csid An identifier for an ID generator instance. * * @return A representation of an ID generator instance resource. */ @GET @Path("/{csid}") @Produces(MediaType.APPLICATION_XML) public Response readIDGenerator(@PathParam("csid") String csid) { ResponseBuilder builder = Response.ok(); Response response = builder.build(); String resourceRepresentation = ""; try { ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(); IDGeneratorInstance instance = service.readIDGenerator(ctx, csid); Document doc = DocumentHelper.createDocument(); Element root = doc.addElement(ID_GENERATOR_NAME); Namespace namespace = new Namespace(ID_SERVICE_NAMESPACE_PREFIX, ID_SERVICE_NAMESPACE); doc.getRootElement().add(namespace); // Make new elements for each of the components of this ID generator // instance, and attach them to the root element. // Append display name information for this ID generator instance. String displayname = instance.getDisplayName(); root = appendDisplayNameIDGeneratorInformation(root, displayname); // Append detailed information for this ID generator instance. root = appendDetailedIDGeneratorInformation(root, instance); resourceRepresentation = XmlTools.prettyPrintXML(doc); response = Response.status(Response.Status.OK).entity(resourceRepresentation) .type(MediaType.APPLICATION_XML).build(); // @TODO Return an XML-based error results format with the // responses below. } catch (DocumentNotFoundException dnfe) { response = Response.status(Response.Status.NOT_FOUND).entity(dnfe.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } catch (IllegalStateException ise) { response = Response.status(Response.Status.BAD_REQUEST).entity(ise.getMessage()) .type(MediaType.TEXT_PLAIN).build(); // This is guard code that should never be reached. } catch (Exception e) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } return response; } ////////////////////////////////////////////////////////////////////// /** * Retrieve a list of available ID Generator instance resources. * * Note: This REST method is required by a HEAD method test * in org.collectionspace.services.client.test.ServiceLayerTest. * * @param format A representation ("format") in which to return * list items, such as a "full" or "summary" format. * * @return A list of representations of ID generator instance resources. */ @GET @Path("") @Produces(MediaType.APPLICATION_XML) public Response readIDGeneratorsList(@QueryParam(QUERY_PARAM_LIST_FORMAT) String listFormat, @QueryParam(QUERY_PARAM_ID_GENERATOR_ROLE) String role) { // @TODO The names and values of the query parameters above // ("format"and "role") are arbitrary, as are the format of the // results returned. These should be standardized and // referenced project-wide. ResponseBuilder builder = Response.ok(); Response response = builder.build(); String resourceRepresentation = ""; try { ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(); Map<String, IDGeneratorInstance> generators = service.readIDGeneratorsList(ctx); // If no ID generator instances were found, return an empty list. if (generators == null || generators.isEmpty()) { Document doc = baseListDocument(); resourceRepresentation = doc.asXML(); response = Response.status(Response.Status.OK).entity(resourceRepresentation) .type(MediaType.APPLICATION_XML).build(); return response; } // Filter list by role // // @TODO Checking for roles here is a short-term expedient; // this should likely be done through a database join on a // table that associates ID generator roles to ID generator // instances. // @TODO The summary list currently returns only CSIDs. // It should additionally return relative URLs, // and possibly also human-readable descriptions. // If the request didn't filter by role, return all // ID generator instances. if (role == null || role.trim().isEmpty()) { // Do nothing // Otherwise, return only ID generator instances // matching the requested role. } else { // @TODO Implement this stubbed code, by // iterating over generator instances and // calling generatorHasRole(). } // Default to summary list if no list format is specified. if (listFormat == null || listFormat.trim().isEmpty()) { resourceRepresentation = formattedSummaryList(generators); } else if (listFormat.equalsIgnoreCase(LIST_FORMAT_SUMMARY)) { resourceRepresentation = formattedSummaryList(generators); } else if (listFormat.equalsIgnoreCase(LIST_FORMAT_FULL)) { resourceRepresentation = formattedFullList(generators); // Return an error if the value of the query parameter // is unrecognized. // // @TODO Return an appropriate XML-based entity body upon error. } else { String msg = "Query parameter '" + listFormat + "' was not recognized."; if (logger.isDebugEnabled()) { logger.debug(msg); } response = Response.status(Response.Status.BAD_REQUEST).entity("").type(MediaType.TEXT_PLAIN) .build(); } response = Response.status(Response.Status.OK).entity(resourceRepresentation) .type(MediaType.APPLICATION_XML).build(); // @TODO Return an XML-based error results format with the // responses below. } catch (IllegalStateException ise) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ise.getMessage()) .type(MediaType.TEXT_PLAIN).build(); // This is guard code that should never be reached. } catch (Exception e) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } return response; } ////////////////////////////////////////////////////////////////////// /** * Deletes an ID generator instance. * */ @DELETE @Path("/{csid}") public Response deleteIDGenerator(@PathParam("csid") String csid) { ResponseBuilder builder = Response.ok(); Response response = builder.build(); try { ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(); service.deleteIDGenerator(ctx, csid); response = Response.ok().entity("").type(MediaType.TEXT_PLAIN).build(); } catch (Exception e) { response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } return response; } ////////////////////////////////////////////////////////////////////// /** * Identifies whether the specified ID generator instance can * generate and validate IDs in a specified role (aka type or context). * * Example: Can a particular ID generator instance generate IDs for * accession numbers? For intake numbers? * * @param csid A CollectionSpace ID (CSID) identifying an * ID generator instance. * * @param role A role (aka type or context) in which that * ID generator instance can generate and * validate IDs. * * @return True if the specified ID generator can generate and validate * IDs in the specified role; false if it cannot. */ private boolean generatorHasRole(String csid, String role) { // @TODO Implement this stubbed method, replacing // this with a lookup of associations of ID generator // instances to ID generator roles; perhaps in the // short term with an external configuration file // and ultimately in a database table. return true; } ////////////////////////////////////////////////////////////////////// /** * Returns a base XML document representing a list of * ID generator instances. * * @return A base XML document representing a list * of ID generator instances. */ private Document baseListDocument() { Document doc = DocumentHelper.createDocument(); Element root = doc.addElement(ID_GENERATOR_LIST_NAME); Namespace namespace = new Namespace(ID_SERVICE_NAMESPACE_PREFIX, ID_SERVICE_NAMESPACE); doc.getRootElement().add(namespace); return doc; } ////////////////////////////////////////////////////////////////////// /** * Returns a list with summary information about ID generator instances. * * This format returns summary data about ID generator * instances, along with relative URIs that may be used * to retrieve more data about each instance. * * @param generators A list of ID generator instances. * * @return A summary list of ID generator instances. */ private String formattedSummaryList(Map<String, IDGeneratorInstance> generators) { Document doc = baseListDocument(); Element listitem = null; String displayname = ""; // Retrieve the CSIDs from each ID generator instance, // and use these in summary information returned about // each instance. for (String csid : generators.keySet()) { // Add a new element for each item in the list. listitem = doc.getRootElement().addElement(ID_GENERATOR_LIST_ITEM_NAME); // Append display name information for this ID generator instance. displayname = generators.get(csid).getDisplayName(); listitem = appendDisplayNameIDGeneratorInformation(listitem, displayname); // Append summary information about this ID generator instance. listitem = appendSummaryIDGeneratorInformation(listitem, csid); } return XmlTools.prettyPrintXML(doc); } ////////////////////////////////////////////////////////////////////// /** * Returns a list with full information about ID generator instances. * * @param generators A list of ID generator instances, each * containing a CollectionSpace ID (CSID). * * @return A full list of ID generator instances. */ private String formattedFullList(Map<String, IDGeneratorInstance> generators) { Document doc = baseListDocument(); Element listitem = null; String displayname = ""; for (String csid : generators.keySet()) { // Add a new element for each item in the list. listitem = doc.getRootElement().addElement(ID_GENERATOR_LIST_ITEM_NAME); // Append display name information for this ID generator instance. displayname = generators.get(csid).getDisplayName(); listitem = appendDisplayNameIDGeneratorInformation(listitem, displayname); // Append summary information about this ID generator instance. listitem = appendSummaryIDGeneratorInformation(listitem, csid); // Append details of this ID generator instance. Element instance = listitem.addElement(ID_GENERATOR_NAME); listitem = appendDetailedIDGeneratorInformation(instance, generators.get(csid)); } return XmlTools.prettyPrintXML(doc); } ////////////////////////////////////////////////////////////////////// /** * Appends a display name to an element representing * an ID generator instance. * * @param instanceElement An XML element representing an * ID generator instance. * * @param displayname A displayname for the resource representing that instance. * * @return The XML element representing an ID generator instance, * with the display name appended. */ private Element appendDisplayNameIDGeneratorInformation(Element instanceElement, String displaynameValue) { Element displayname = instanceElement.addElement("displayname"); displayname.addText(displaynameValue); return instanceElement; } ////////////////////////////////////////////////////////////////////// /** * Appends summary information to an element representing * an ID generator instance. * * @param instanceElement An XML element representing an * ID generator instance. * * @param csid A CollectionSpace ID (CISD) associated with * the resource representing that instance. * * @return The XML element representing an ID generator instance, * with summary information appended. */ private Element appendSummaryIDGeneratorInformation(Element instanceElement, String csidValue) { Element uri = instanceElement.addElement("uri"); uri.addText(getRelativePath(csidValue)); Element csid = instanceElement.addElement("csid"); csid.addText(csidValue); return instanceElement; } ////////////////////////////////////////////////////////////////////// /** * Appends detailed information about an ID generator instance, * to an element representing that ID generator instance. * * @param instanceElement An XML element representing an * ID generator instance. * * @param generatorInstance An instance of an ID generator. * * @return The XML element representing an ID generator instance, * with detailed information appended. */ private Element appendDetailedIDGeneratorInformation(Element instanceElement, IDGeneratorInstance generatorInstance) { // Append description information. Element description = instanceElement.addElement("description"); description.addText(generatorInstance.getDescription()); // Append a representative, or sample, ID - of a type that // can be generated by this ID generator instance - for display. Element displayid = instanceElement.addElement("displayid"); // Return the last generated ID as a representative ID. // If no ID has ever been generated by this ID generator instance, // return the current ID instead. // // @TODO This is a short-term kludge. We may wish to instead // generate a static, sample ID, at system initialization // or launch time; or generate or load this value once, at the // time that an ID generator instance is created. String lastgenerated = generatorInstance.getLastGeneratedID(); if (lastgenerated != null & !lastgenerated.trim().isEmpty()) { displayid.addText(lastgenerated); } else { SettableIDGenerator gen; try { gen = IDGeneratorSerializer.deserialize(generatorInstance.getGeneratorState()); String current = gen.getCurrentID(); if (current != null & !current.trim().isEmpty()) { displayid.addText(current); } } catch (Exception e) { // Do nothing here. // @TODO // Could potentially return an error message, akin to: // displayid.addText("No ID available for display"); } } // Append components information. Element generator = instanceElement.addElement(ID_GENERATOR_COMPONENTS_NAME); // Get an XML string representation of the ID generator's components. String generatorStr = generatorInstance.getGeneratorState(); // Convert the XML string representation of the ID generator's // components to a new XML document, copy its root element, and // append it to the relevant location within the current element. try { Document generatorDoc = XmlTools.textToXMLDocument(generatorStr); Element generatorRoot = generatorDoc.getRootElement(); generator.add(generatorRoot.createCopy()); // If an error occurs parsing the XML string representation, // the text of the components element will remain empty. } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Error parsing XML text: " + generatorStr); } } return instanceElement; } ////////////////////////////////////////////////////////////////////// /** * Returns a relative URI path to a resource * that represents an instance of an ID generator. * * @param csid A CollectionSpace ID (CSID). * * @return A relative URI path to a resource that * represents an ID generator instance. */ private String getRelativePath(String csid) { // @TODO Verify that this is the correct relative path. // Do we need to check the path provided in the original request? if (csid != null && !csid.trim().isEmpty()) { return BASE_URL_PATH + "/" + csid; } else { return BASE_URL_PATH; } } @Override public Class<?> getCommonPartClass() { // TODO Auto-generated method stub return null; } @Override public ServiceContextFactory<PoxPayloadIn, PoxPayloadOut> getServiceContextFactory() { return MultipartServiceContextFactory.get(); } @Override public String getServiceName() { return IdClient.SERVICE_NAME; } @Override protected String getVersionString() { // TODO Auto-generated method stub return null; } }