2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common;
26 import java.math.BigInteger;
27 import java.util.HashMap;
28 import java.util.List;
31 import javax.ws.rs.GET;
32 import javax.ws.rs.Path;
33 import javax.ws.rs.Produces;
34 import javax.ws.rs.core.CacheControl;
35 import javax.ws.rs.core.Context;
36 import javax.ws.rs.core.MultivaluedMap;
37 import javax.ws.rs.core.Response;
38 import javax.ws.rs.core.UriInfo;
40 import org.collectionspace.services.client.CollectionSpaceClient;
41 import org.collectionspace.services.common.CSWebApplicationException;
42 import org.collectionspace.services.common.api.Tools;
43 import org.collectionspace.services.common.config.ServiceConfigUtils;
44 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
45 import org.collectionspace.services.common.context.ServiceContext;
46 import org.collectionspace.services.common.context.ServiceContextProperties;
47 import org.collectionspace.services.common.document.BadRequestException;
48 import org.collectionspace.services.common.document.DocumentException;
49 import org.collectionspace.services.common.document.DocumentHandler;
50 import org.collectionspace.services.common.document.DocumentNotFoundException;
51 import org.collectionspace.services.common.document.TransactionException;
52 import org.collectionspace.services.common.repository.RepositoryClient;
53 import org.collectionspace.services.common.repository.RepositoryClientFactory;
54 import org.collectionspace.services.common.security.UnauthorizedException;
55 import org.collectionspace.services.common.storage.StorageClient;
56 import org.collectionspace.services.common.storage.jpa.JpaStorageClientImpl;
57 import org.collectionspace.services.config.service.ServiceBindingType;
58 import org.collectionspace.services.config.service.DocHandlerParams.Params;
59 import org.collectionspace.services.description.ServiceDescription;
61 import org.jboss.resteasy.spi.HttpRequest;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * The Class AbstractCollectionSpaceResourceImpl.
68 * @param <IT> the generic type
69 * @param <OT> the generic type
71 public abstract class AbstractCollectionSpaceResourceImpl<IT, OT>
72 implements CollectionSpaceResource<IT, OT> {
74 protected final Logger logger = LoggerFactory.getLogger(this.getClass());
76 protected final ServiceContext<IT, OT> NULL_CONTEXT = null;
77 // Fields for default client factory and client
78 /** The repository client factory. */
79 private RepositoryClientFactory<IT, OT> repositoryClientFactory;
81 /** The repository client. */
82 private RepositoryClient<IT, OT> repositoryClient;
84 /** The storage client. */
85 private StorageClient storageClient;
93 protected static String extractId(Response res) {
94 MultivaluedMap<String, Object> mvm = res.getMetadata();
95 String uri = (String) ((List<Object>) mvm.get("Location")).get(0);
96 String[] segments = uri.split("/");
97 String id = segments[segments.length - 1];
102 * Instantiates a new abstract collection space resource.
104 public AbstractCollectionSpaceResourceImpl() {
105 repositoryClientFactory = (RepositoryClientFactory<IT, OT>) RepositoryClientFactory.getInstance();
109 * @see org.collectionspace.services.common.CollectionSpaceResource#getServiceName()
112 abstract public String getServiceName();
116 * @see org.collectionspace.services.common.CollectionSpaceResource#getRepositoryClient(org.collectionspace.services.common.context.ServiceContext)
119 synchronized public RepositoryClient<IT, OT> getRepositoryClient(ServiceContext<IT, OT> ctx) {
120 if (repositoryClient != null){
121 return repositoryClient;
123 repositoryClient = repositoryClientFactory.getClient(ctx.getRepositoryClientName());
124 return repositoryClient;
128 * @see org.collectionspace.services.common.CollectionSpaceResource#getStorageClient(org.collectionspace.services.common.context.ServiceContext)
131 synchronized public StorageClient getStorageClient(ServiceContext<IT, OT> ctx) {
132 if(storageClient != null) {
133 return storageClient;
135 storageClient = new JpaStorageClientImpl();
136 return storageClient;
140 * @see org.collectionspace.services.common.CollectionSpaceResource#createDocumentHandler(org.collectionspace.services.common.context.ServiceContext)
143 public DocumentHandler createDocumentHandler(ServiceContext<IT, OT> ctx) throws Exception {
144 DocumentHandler docHandler = createDocumentHandler(ctx, ctx.getInput());
149 * Creates the document handler.
152 * @param commonPart the common part
154 * @return the document handler
156 * @throws Exception the exception
158 public DocumentHandler createDocumentHandler(ServiceContext<IT, OT> ctx,
159 Object commonPart) throws Exception {
160 DocumentHandler docHandler = ctx.getDocumentHandler();
161 docHandler.setCommonPart(commonPart);
166 * Creates the service context.
168 * @return the service context< i t, o t>
170 * @throws Exception the exception
172 protected ServiceContext<IT, OT> createServiceContext() throws Exception {
173 ServiceContext<IT, OT> ctx = createServiceContext(this.getServiceName(),
174 (IT)null, //inputType
175 null, // The resource map
176 (UriInfo)null, // The query params
177 this.getCommonPartClass());
182 * Creates the service context.
184 * @param serviceName the service name
186 * @return the service context< i t, o t>
188 * @throws Exception the exception
190 public ServiceContext<IT, OT> createServiceContext(String serviceName) throws Exception {
191 ServiceContext<IT, OT> ctx = createServiceContext(
193 (IT)null, // The input part
194 null, // The resource map
195 (UriInfo)null, // The queryParams
196 (Class<?>)null /*input type's Class*/);
200 protected ServiceContext<IT, OT> createServiceContext(String serviceName, UriInfo ui) throws Exception {
201 ServiceContext<IT, OT> ctx = createServiceContext(
203 (IT)null, // The input part
204 null, // The resource map
205 (UriInfo)null, // The queryParams
206 (Class<?>)null /*input type's Class*/);
212 * Creates the service context.
214 * @param serviceName the service name
215 * @param input the input
217 * @return the service context< i t, o t>
219 * @throws Exception the exception
221 protected ServiceContext<IT, OT> createServiceContext(String serviceName,
222 IT input) throws Exception {
223 ServiceContext<IT, OT> ctx = createServiceContext(serviceName,
225 null, // The resource map
226 (UriInfo)null, /*queryParams*/
227 (Class<?>)null /*input type's Class*/);
231 protected ServiceContext<IT, OT> createServiceContext(String serviceName,
233 UriInfo uriInfo) throws Exception {
234 ServiceContext<IT, OT> ctx = createServiceContext(serviceName,
236 null, // The resource map
237 uriInfo, /*queryParams*/
238 (Class<?>)null /*input type's Class*/);
242 protected ServiceContext<IT, OT> createServiceContext(UriInfo uriInfo) throws Exception {
243 ServiceContext<IT, OT> ctx = createServiceContext(
246 (Class<?>)null /*input type's Class*/);
251 * Creates the service context.
253 * @param input the input
255 * @return the service context< i t, o t>
257 * @throws Exception the exception
259 protected ServiceContext<IT, OT> createServiceContext(IT input) throws Exception {
260 ServiceContext<IT, OT> ctx = createServiceContext(
262 (Class<?>)null /*input type's Class*/);
266 protected ServiceContext<IT, OT> createServiceContext(IT input, UriInfo uriInfo) throws Exception {
267 ServiceContext<IT, OT> ctx = createServiceContext(
270 null ); // The class param/argument
275 * Creates the service context.
277 * @param input the input
278 * @param theClass the the class
280 * @return the service context
282 * @throws Exception the exception
284 protected ServiceContext<IT, OT> createServiceContext(IT input, Class<?> theClass) throws Exception {
285 ServiceContext<IT, OT> ctx = createServiceContext(
287 (UriInfo)null, //queryParams,
292 protected ServiceContext<IT, OT> createServiceContext(IT input, Class<?> theClass, UriInfo uriInfo) throws Exception {
293 ServiceContext<IT, OT> ctx = createServiceContext(
300 protected ServiceContext<IT, OT> createServiceContext(
302 ResourceMap resourceMap,
303 UriInfo uriInfo) throws Exception {
304 ServiceContext<IT, OT> ctx = createServiceContext(
306 null, // The input object
309 null /* the class of the input type */);
313 protected ServiceContext<IT, OT> createServiceContext(
315 ResourceMap resourceMap,
316 UriInfo uriInfo) throws Exception {
317 ServiceContext<IT, OT> ctx = createServiceContext(
318 this.getServiceName(),
322 null /* the class of the input type */);
326 protected ServiceContext<IT, OT> createServiceContext(
329 ResourceMap resourceMap,
330 UriInfo uriInfo) throws Exception {
331 ServiceContext<IT, OT> ctx = createServiceContext(
336 null /* the class of the input type */);
341 * Creates the service context.
343 * @param input the input
344 * @param queryParams the query params
345 * @param theClass the the class
347 * @return the service context< i t, o t>
349 * @throws Exception the exception
351 private ServiceContext<IT, OT> createServiceContext(
354 Class<?> theClass) throws Exception {
355 return createServiceContext(this.getServiceName(),
357 null, // The resource map
363 * Creates the service context.
365 * @param serviceName the service name
366 * @param input the input
367 * @param queryParams the query params
368 * @param theClass the the class
370 * @return the service context< i t, o t>
372 * @throws Exception the exception
374 private ServiceContext<IT, OT> createServiceContext(
377 ResourceMap resourceMap,
379 Class<?> theClass) throws Exception {
380 ServiceContext<IT, OT> ctx = getServiceContextFactory().createServiceContext(
385 theClass != null ? theClass.getPackage().getName() : null,
386 theClass != null ? theClass.getName() : null);
387 if (theClass != null) {
388 ctx.setProperty(ServiceContextProperties.ENTITY_CLASS, theClass);
395 * Gets the version string.
397 * @return the version string
399 abstract protected String getVersionString();
404 * @return the version
408 @Produces("application/xml")
409 public Version getVersion() {
410 Version result = new Version();
412 result.setVersionString(getVersionString());
418 * Get the service description
421 @Path(CollectionSpaceClient.SERVICE_DESCRIPTION_PATH)
422 public ServiceDescription getDescription(@Context UriInfo uriInfo) {
423 ServiceDescription result = null;
425 ServiceContext<IT, OT> ctx = null;
427 ctx = createServiceContext(uriInfo);
428 result = getDescription(ctx);
429 } catch (Exception e) {
430 String errMsg = String.format("Request to get service description information for the '%s' service failed.",
431 this.getServiceContextFactory());
432 throw bigReThrow(e, errMsg);
439 * Each resource can override this method if they need to.
444 public ServiceDescription getDescription(ServiceContext<IT, OT> ctx) {
445 ServiceDescription result = new ServiceDescription();
447 result.setDocumentType(getDocType(ctx.getTenantId()));
452 public void checkResult(Object resultToCheck, String csid, String serviceMessage) throws CSWebApplicationException {
453 if (resultToCheck == null) {
454 Response response = Response.status(Response.Status.NOT_FOUND).entity(
455 serviceMessage + "csid=" + csid
456 + ": was not found.").type(
457 "text/plain").build();
458 throw new CSWebApplicationException(response);
462 protected void ensureCSID(String csid, String crudType) throws CSWebApplicationException {
463 ensureCSID(csid, crudType, "csid");
466 protected void ensureCSID(String csid, String crudType, String whichCsid) throws CSWebApplicationException {
467 if (logger.isDebugEnabled()) {
468 logger.debug(crudType + " for " + getClass().getName() + " with csid=" + csid);
470 if (csid == null || "".equals(csid)) {
471 logger.error(crudType + " for " + getClass().getName() + " missing csid!");
472 Response response = Response.status(Response.Status.BAD_REQUEST).entity(crudType + " failed on " + getClass().getName() + ' '+whichCsid+'=' + csid).type("text/plain").build();
473 throw new CSWebApplicationException(response);
477 protected CSWebApplicationException bigReThrow(Exception e, String serviceMsg) throws CSWebApplicationException {
478 return bigReThrow(e, serviceMsg, "");
481 protected CSWebApplicationException bigReThrow(Exception e, String serviceMsg, String csid) throws CSWebApplicationException {
482 boolean logException = true;
483 CSWebApplicationException result = null;
485 String detail = Tools.errorToString(e, true);
486 String detailNoTrace = Tools.errorToString(e, true, 3);
488 if (e instanceof UnauthorizedException) {
489 response = Response.status(Response.Status.UNAUTHORIZED).entity(serviceMsg + e.getMessage()).type("text/plain").build();
490 result = new CSWebApplicationException(e, response);
492 } else if (e instanceof DocumentNotFoundException) {
494 // Don't log this error unless we're in 'trace' mode
496 logException = false;
497 response = Response.status(Response.Status.NOT_FOUND).entity(serviceMsg + " on " + getClass().getName() + " csid=" + csid).type("text/plain").build();
498 result = new CSWebApplicationException(e, response);
500 } else if (e instanceof TransactionException) {
501 int code = ((TransactionException) e).getErrorCode();
502 response = Response.status(code).entity(e.getMessage()).type("text/plain").build();
503 result = new CSWebApplicationException(e, response);
505 } else if (e instanceof BadRequestException) {
506 int code = ((BadRequestException) e).getErrorCode();
508 code = Response.Status.BAD_REQUEST.getStatusCode();
511 response = Response.status(code).entity(serviceMsg + e.getMessage()).type("text/plain").build();
512 // return new WebApplicationException(e, code);
513 result = new CSWebApplicationException(e, response);
515 } else if (e instanceof DocumentException) {
516 int code = ((DocumentException) e).getErrorCode();
518 code = Response.Status.BAD_REQUEST.getStatusCode();
521 response = Response.status(code).entity(serviceMsg + e.getMessage()).type("text/plain").build();
522 // return new WebApplicationException(e, code);
523 result = new CSWebApplicationException(e, response);
525 } else if (e instanceof CSWebApplicationException) {
526 // subresource may have already thrown this exception
527 // so just pass it on
528 result = (CSWebApplicationException) e;
530 } else { // e is now instanceof Exception
531 response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(serviceMsg + " detail: " + detailNoTrace).type("text/plain").build();
532 result = new CSWebApplicationException(e, response);
535 // Some exceptions like DocumentNotFoundException won't be logged unless we're in 'trace' mode
537 boolean traceEnabled = logger.isTraceEnabled();
538 if (logException == true || traceEnabled == true) {
539 if (traceEnabled == true) {
540 logger.error(getClass().getName() + " detail: " + detail, e);
542 logger.error(getClass().getName() + " detail: " + detailNoTrace);
550 public boolean allowAnonymousAccess(HttpRequest request,
551 Class<?> resourceClass) {
556 * Returns a UriRegistry entry: a map of tenant-qualified URI templates
557 * for the current resource, for all tenants
559 * @return a map of URI templates for the current resource, for all tenants
561 public Map<UriTemplateRegistryKey,StoredValuesUriTemplate> getUriRegistryEntries() {
562 Map<UriTemplateRegistryKey,StoredValuesUriTemplate> uriRegistryEntriesMap =
563 new HashMap<UriTemplateRegistryKey,StoredValuesUriTemplate>();
564 List<String> tenantIds = getTenantBindingsReader().getTenantIds();
565 for (String tenantId : tenantIds) {
566 uriRegistryEntriesMap.putAll(getUriRegistryEntries(tenantId, getDocType(tenantId), UriTemplateFactory.RESOURCE));
568 return uriRegistryEntriesMap;
572 * Returns a resource's document type.
578 public String getDocType(String tenantId) {
579 return getDocType(tenantId, getServiceName());
583 * Returns the document type associated with a specified service, within a specified tenant.
585 * @param tenantId a tenant ID
586 * @param serviceName a service name
587 * @return the Nuxeo document type associated with that service and tenant.
589 // FIXME: This method may properly belong in a different services package or class.
590 // Also, we need to check for any existing methods that may duplicate this one.
591 protected String getDocType(String tenantId, String serviceName) {
593 if (Tools.isBlank(tenantId)) {
596 ServiceBindingType sb = getTenantBindingsReader().getServiceBinding(tenantId, serviceName);
600 docType = sb.getObject().getName(); // Reads the Document Type from tenant bindings configuration
605 * Returns a UriRegistry entry: a map of tenant-qualified URI templates
606 * for the current resource, for a specified tenants
608 * @return a map of URI templates for the current resource, for a specified tenant
611 public Map<UriTemplateRegistryKey,StoredValuesUriTemplate> getUriRegistryEntries(String tenantId,
612 String docType, UriTemplateFactory.UriTemplateType type) {
613 Map<UriTemplateRegistryKey,StoredValuesUriTemplate> uriRegistryEntriesMap =
614 new HashMap<UriTemplateRegistryKey,StoredValuesUriTemplate>();
615 UriTemplateRegistryKey key;
616 if (Tools.isBlank(tenantId) || Tools.isBlank(docType)) {
617 return uriRegistryEntriesMap;
619 key = new UriTemplateRegistryKey();
620 key.setTenantId(tenantId);
621 key.setDocType(docType);
622 uriRegistryEntriesMap.put(key, getUriTemplate(type));
623 return uriRegistryEntriesMap;
627 * Returns a URI template of the appropriate type, populated with the
628 * current service name as one of its stored values.
630 * @param type a URI template type
631 * @return a URI template of the appropriate type.
634 public StoredValuesUriTemplate getUriTemplate(UriTemplateFactory.UriTemplateType type) {
635 Map<String,String> storedValuesMap = new HashMap<String,String>();
636 storedValuesMap.put(UriTemplateFactory.SERVICENAME_VAR, getServiceName());
637 StoredValuesUriTemplate template =
638 UriTemplateFactory.getURITemplate(type, storedValuesMap);
643 * Returns a reader for reading values from tenant bindings configuration
645 * @return a tenant bindings configuration reader
648 public TenantBindingConfigReaderImpl getTenantBindingsReader() {
649 return ServiceMain.getInstance().getTenantBindingConfigReader();
653 * Get max cache age for HTTP responses from the tenant's service bindings.
658 protected int getCacheMaxAge(ServiceContext<IT, OT> ctx) {
659 BigInteger result = null;
662 Params docHandlerParams = ServiceConfigUtils.getDocHandlerParams(ctx.getTenantId(), ctx.getServiceName());
663 if (docHandlerParams.getCacheMaxAge() != null) {
664 result = docHandlerParams.getCacheMaxAge();
666 } catch (DocumentException e) {
667 logger.debug("Failed to retrieve cache-age-max from service bindings.", e);
670 return result != null ? result.intValue() : 0;
673 protected Response.ResponseBuilder setCacheControl(ServiceContext<IT, OT> ctx, Response.ResponseBuilder responseBuilder) {
674 int cacheMaxAge = getCacheMaxAge(ctx);
676 if (cacheMaxAge > 0) {
677 CacheControl cacheControl = new CacheControl();
678 cacheControl.setMaxAge(getCacheMaxAge(ctx));
679 responseBuilder.cacheControl(cacheControl);
680 logger.debug(String.format("Cache-max-age for service '%s' is set to '%d' in the service bindings for tenant ID='%s'.",
681 ctx.getServiceName(), cacheMaxAge, ctx.getTenantId()));
684 return responseBuilder;