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.context;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
31 import javax.ws.rs.core.MultivaluedMap;
32 import javax.ws.rs.core.UriInfo;
34 import org.collectionspace.authentication.spi.AuthNContext;
35 import org.collectionspace.services.client.AuthorityClient;
36 import org.collectionspace.services.client.IClientQueryParams;
37 import org.collectionspace.services.client.IQueryManager;
38 import org.collectionspace.services.client.workflow.WorkflowClient;
39 import org.collectionspace.services.common.ServiceMain;
40 import org.collectionspace.services.common.authorization_mgt.AuthorizationCommon;
41 import org.collectionspace.services.common.config.PropertyItemUtils;
42 import org.collectionspace.services.common.config.ServiceConfigUtils;
43 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
44 import org.collectionspace.services.common.document.DocumentHandler;
45 import org.collectionspace.services.common.document.DocumentFilter;
46 import org.collectionspace.services.common.document.ValidatorHandler;
47 import org.collectionspace.services.common.security.SecurityContext;
48 import org.collectionspace.services.common.security.SecurityContextImpl;
49 import org.collectionspace.services.common.security.UnauthorizedException;
50 import org.collectionspace.services.config.ClientType;
51 import org.collectionspace.services.config.service.ObjectPartType;
52 import org.collectionspace.services.config.service.ServiceBindingType;
53 import org.collectionspace.services.config.tenant.RepositoryDomainType;
54 import org.collectionspace.services.config.tenant.TenantBindingType;
55 import org.collectionspace.services.config.types.PropertyItemType;
56 import org.collectionspace.services.config.types.PropertyType;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * AbstractServiceContext
63 * $LastChangedRevision: $
78 public abstract class AbstractServiceContextImpl<IT, OT>
79 implements ServiceContext<IT, OT> {
82 final Logger logger = LoggerFactory.getLogger(AbstractServiceContextImpl.class);
84 /** The properties. */
85 Map<String, Object> properties = new HashMap<String, Object>();
86 /** The object part map. */
87 Map<String, ObjectPartType> objectPartMap = new HashMap<String, ObjectPartType>();
88 /** The service binding. */
89 protected ServiceBindingType serviceBinding;
90 /** The tenant binding. */
91 private TenantBindingType tenantBinding;
92 /** repository domain used by the service */
93 private RepositoryDomainType repositoryDomain;
94 /** The override document type. */
95 private String overrideDocumentType = null;
96 /** The val handlers. */
97 private List<ValidatorHandler<IT, OT>> valHandlers = null;
98 /** The authority client -use for shared authority server */
99 private AuthorityClient authorityClient = null;
100 /** The doc handler. */
101 private DocumentHandler docHandler = null;
102 /** security context */
103 private SecurityContext securityContext;
104 /** The sessions JAX-RS URI information */
105 private UriInfo uriInfo;
106 /** The current repository session */
107 private Object currentRepositorySession;
108 /** A reference count for the current repository session */
109 private int currentRepoSesssionRefCount = 0;
112 * Instantiates a new abstract service context impl.
114 private AbstractServiceContextImpl() {
115 // private constructor for singleton pattern
117 // request query params
118 /** The query params. */
119 private MultivaluedMap<String, String> queryParams;
122 * Instantiates a new abstract service context impl.
124 * @param serviceName the service name
126 * @throws UnauthorizedException the unauthorized exception
128 protected AbstractServiceContextImpl(String serviceName, UriInfo uriInfo) throws UnauthorizedException {
130 //establish security context
131 securityContext = new SecurityContextImpl(uriInfo);
132 //make sure tenant context exists
133 checkTenantContext();
135 String tenantId = securityContext.getCurrentTenantId();
136 if (AuthorizationCommon.ALL_TENANTS_MANAGER_TENANT_ID.equals(tenantId) ||
137 AuthNContext.ANONYMOUS_TENANT_ID.equals(tenantId)) {
138 // Tenant Manager has no tenant binding, so don't bother...
139 tenantBinding = null;
140 serviceBinding = null;
141 repositoryDomain = null;
143 //retrieve service bindings
144 TenantBindingConfigReaderImpl tReader =
145 ServiceMain.getInstance().getTenantBindingConfigReader();
146 tenantBinding = tReader.getTenantBinding(tenantId);
147 if (tenantBinding == null) {
148 String msg = "No tenant binding found for tenantId=" + tenantId
149 + " while processing request for service= " + serviceName;
151 throw new IllegalStateException(msg);
153 serviceBinding = tReader.getServiceBinding(tenantId, serviceName);
154 if (serviceBinding == null) {
155 String msg = "No service binding found while processing request for "
156 + serviceName + " for tenant id=" + getTenantId()
157 + " name=" + getTenantName();
159 throw new IllegalStateException(msg);
161 if (logger.isDebugEnabled()) {
162 logger.debug("tenantId=" + tenantId
163 + " service binding=" + serviceBinding.getName());
165 repositoryDomain = tReader.getRepositoryDomain(tenantId, serviceName);
166 if (repositoryDomain != null) {
167 if (logger.isDebugEnabled()) {
168 logger.debug("tenantId=" + tenantId
169 + " repository doamin=" + repositoryDomain.getName());
175 public int getTimeoutParam(UriInfo ui) {
176 int result = DEFAULT_TX_TIMEOUT;
178 MultivaluedMap<String, String> queryParams = (ui == null) ? null : ui.getQueryParameters();
179 if (queryParams != null) {
180 String timeoutString = queryParams.getFirst(IClientQueryParams.IMPORT_TIMEOUT_PARAM);
181 if (timeoutString != null)
183 result = Integer.parseInt(timeoutString);
184 } catch (NumberFormatException e) {
185 logger.warn("Transaction timeout period parameter could not be parsed. The characters in the parameter string must all be decimal digits. The Import service will use the default timeout period instead.",
194 public int getTimeoutSecs() {
195 UriInfo uriInfo = this.getUriInfo();
196 return this.getTimeoutParam(uriInfo);
200 * @see org.collectionspace.services.common.context.ServiceContext#getCommonPartLabel()
203 public String getCommonPartLabel() {
204 return getCommonPartLabel(getServiceName());
208 * @see org.collectionspace.services.common.context.ServiceContext#getCommonPartLabel(java.lang.String)
210 public String getCommonPartLabel(String schemaName) {
211 return schemaName.toLowerCase() + PART_LABEL_SEPARATOR + PART_COMMON_LABEL;
215 * @see org.collectionspace.services.common.context.ServiceContext#getPartsMetadata()
218 public Map<String, ObjectPartType> getPartsMetadata() {
219 if (objectPartMap.size() != 0) {
220 return objectPartMap;
222 ServiceBindingUtils.getPartsMetadata(getServiceBinding(), objectPartMap);
223 return objectPartMap;
227 * Gets the properties for part.
229 * @param partLabel the part label
231 * @return the properties for part
233 public List<PropertyItemType> getPropertiesForPart(String partLabel) {
234 Map<String, ObjectPartType> partMap = getPartsMetadata();
235 ObjectPartType part = partMap.get(partLabel);
237 throw new RuntimeException("No such part found: " + partLabel);
239 List<PropertyType> propNodeList = part.getProperties();
240 return propNodeList.isEmpty() ? null : propNodeList.get(0).getItem();
244 * @param partLabel The name of the scehma part to search in
245 * @param propName The name of the property (or properties) to find
246 * @param qualified Whether the returned values should be qualified with the
247 * partLabel. This is when the property values are schema field references.
248 * @return List of property values for the matched property on the named schema part.
250 public List<String> getPropertyValuesForPart(String partLabel, String propName, boolean qualified) {
251 List<PropertyItemType> allProps = getPropertiesForPart(partLabel);
252 return PropertyItemUtils.getPropertyValuesByName(allProps, propName,
253 (qualified ? (partLabel + ":") : null));
257 * @param propName The name of the property (or properties) to find
258 * @param qualified Whether the returned values should be qualified with the
259 * partLabel. This is when the property values are schema field references.
260 * @return List of property values for the matched property on any schema part.
262 public List<String> getAllPartsPropertyValues(String propName, boolean qualified) {
263 return ServiceBindingUtils.getAllPartsPropertyValues(getServiceBinding(), propName, qualified);
267 * @see org.collectionspace.services.common.context.ServiceContext#getServiceBindingPropertyValue(java.lang.String)
269 public String getServiceBindingPropertyValue(String propName) {
270 return ServiceBindingUtils.getPropertyValue(getServiceBinding(), propName);
274 * Gets the common part properties.
276 * @return the common part properties
278 public List<PropertyItemType> getCommonPartProperties() {
279 return getPropertiesForPart(getCommonPartLabel());
283 * @param propName The name of the property (or properties) to find
284 * @param qualified Whether the returned values should be qualified with the
285 * partLabel. This is when the property values are schema field references.
286 * @return List of property values for the matched property on the common schema part.
288 public List<String> getCommonPartPropertyValues(String propName, boolean qualified) {
289 return getPropertyValuesForPart(getCommonPartLabel(), propName, qualified);
293 * @see org.collectionspace.services.common.context.ServiceContext#getQualifiedServiceName()
296 public String getQualifiedServiceName() {
297 return TenantBindingConfigReaderImpl.getTenantQualifiedServiceName(getTenantId(), getServiceName());
301 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryClientName()
304 public String getRepositoryClientName() {
305 if (repositoryDomain == null) {
308 return repositoryDomain.getRepositoryClient();
312 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryClientType()
315 public ClientType getRepositoryClientType() {
316 //assumption: there is only one repository client configured
317 return ServiceMain.getInstance().getClientType();
321 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryDomainName()
324 public String getRepositoryDomainName() {
325 if (repositoryDomain == null) {
328 return repositoryDomain.getName();
332 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryDomainName()
335 public String getRepositoryDomainStorageName() {
336 if (repositoryDomain == null) {
339 return repositoryDomain.getStorageName();
343 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryWorkspaceId()
346 public String getRepositoryWorkspaceId() {
347 return ServiceMain.getInstance().getWorkspaceId(getTenantId(), getServiceName());
351 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryWorkspaceName()
354 public String getRepositoryWorkspaceName() {
355 //service name is workspace name by convention
356 return serviceBinding.getName();
360 * @see org.collectionspace.services.common.context.ServiceContext#getServiceBinding()
363 public ServiceBindingType getServiceBinding() {
364 return serviceBinding;
368 * @see org.collectionspace.services.common.context.ServiceContext#getServiceName()
371 public String getServiceName() {
372 return serviceBinding.getName();
376 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentType()
379 public String getDocumentType() {
380 // If they have not overridden the setting, use the type of the service
382 return (overrideDocumentType != null) ? overrideDocumentType : serviceBinding.getObject().getName();
386 public String getTenantQualifiedDoctype(String docType) {
387 // If they have not overridden the setting, use the type of the service
389 String result = ServiceBindingUtils.getTenantQualifiedDocType(this.getTenantId(), docType);
395 public String getTenantQualifiedDoctype() {
396 String docType = (overrideDocumentType != null) ? overrideDocumentType : serviceBinding.getObject().getName();
397 return getTenantQualifiedDoctype(docType);
401 * @see org.collectionspace.services.common.context.ServiceContext#setDocumentType(java.lang.String)
404 public void setDocumentType(String docType) {
405 overrideDocumentType = docType;
409 * @see org.collectionspace.services.common.context.ServiceContext#getSecurityContext()
412 public SecurityContext getSecurityContext() {
413 return securityContext;
417 * @see org.collectionspace.services.common.context.ServiceContext#getUserId()
420 public String getUserId() {
421 return securityContext.getUserId();
425 * @see org.collectionspace.services.common.context.ServiceContext#getTenantId()
428 public String getTenantId() {
429 return securityContext.getCurrentTenantId();
433 * @see org.collectionspace.services.common.context.ServiceContext#getTenantName()
436 public String getTenantName() {
437 return securityContext.getCurrentTenantName();
441 * @see org.collectionspace.services.common.context.ServiceContext#getInput()
444 public abstract IT getInput();
447 * @see org.collectionspace.services.common.context.ServiceContext#setInput(java.lang.Object)
450 public abstract void setInput(IT input);
453 * @see org.collectionspace.services.common.context.ServiceContext#getOutput()
456 public abstract OT getOutput();
459 * @see org.collectionspace.services.common.context.ServiceContext#setOutput(java.lang.Object)
462 public abstract void setOutput(OT output);
465 * @see org.collectionspace.services.common.context.ServiceContext#getProperties()
468 public Map<String, Object> getProperties() {
473 * @see org.collectionspace.services.common.context.ServiceContext#setProperties(java.util.Map)
476 public void setProperties(Map<String, Object> props) {
477 properties.putAll(props);
481 * @see org.collectionspace.services.common.context.ServiceContext#getProperty(java.lang.String)
483 public Object getProperty(String name) {
484 return properties.get(name);
488 * @see org.collectionspace.services.common.context.ServiceContext#setProperty(java.lang.String, java.lang.Object)
490 public void setProperty(String name, Object o) {
491 properties.put(name, o);
495 * checkTenantContext makss sure tenant context exists
499 * @throws UnauthorizedException the unauthorized exception
501 private void checkTenantContext() throws UnauthorizedException {
503 String tenantId = securityContext.getCurrentTenantId();
504 if (tenantId == null) {
505 String msg = "Could not find tenant context";
507 throw new UnauthorizedException(msg);
511 private static String buildWorkflowWhereClause(MultivaluedMap<String, String> queryParams) {
512 String result = null;
514 String includeDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_NONDELETED);
515 if (includeDeleted != null && includeDeleted.equalsIgnoreCase(Boolean.FALSE.toString())) {
516 result = "ecm:currentLifeCycleState <> 'deleted'";
523 * Creates the document handler instance.
525 * @return the document handler
527 * @throws Exception the exception
529 private DocumentHandler createDocumentHandlerInstance() throws Exception {
530 docHandler = ServiceConfigUtils.createDocumentHandlerInstance(tenantBinding, serviceBinding);
532 // Create a default document filter
534 docHandler.setServiceContext(this);
535 DocumentFilter docFilter = docHandler.createDocumentFilter();
537 // If the context was created with query parameters,
538 // reflect the values of those parameters in the document filter
539 // to specify sort ordering, pagination, etc.
541 if (this.getQueryParams() != null) {
542 docFilter.setSortOrder(this.getQueryParams());
543 docFilter.setPagination(this.getQueryParams());
544 String workflowWhereClause = buildWorkflowWhereClause(queryParams);
545 if (workflowWhereClause != null) {
546 docFilter.appendWhereClause(workflowWhereClause, IQueryManager.SEARCH_QUALIFIER_AND);
550 docHandler.setDocumentFilter(docFilter);
556 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHandler()
559 public DocumentHandler getDocumentHandler() throws Exception {
560 DocumentHandler result = docHandler;
561 // create a new instance if one does not yet exist
562 if (result == null) {
563 result = createDocumentHandlerInstance();
569 public void setDocumentHandler(DocumentHandler handler) throws Exception {
570 if (handler != null) {
571 docHandler = handler;
576 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHanlder(javax.ws.rs.core.MultivaluedMap)
579 public DocumentHandler getDocumentHandler(MultivaluedMap<String, String> queryParams) throws Exception {
580 DocumentHandler result = getDocumentHandler();
581 DocumentFilter documentFilter = result.getDocumentFilter(); //to see results in debugger variables view
582 documentFilter.setPagination(queryParams);
587 * If this element is set in the service binding then use it otherwise
588 * assume that asserts are NOT disabled.
590 private boolean disableValidationAsserts() {
592 Boolean disableAsserts = getServiceBinding().isDisableAsserts();
593 result = (disableAsserts != null) ? disableAsserts : false;
598 * @see org.collectionspace.services.common.context.ServiceContext#getValidatorHandlers()
601 public List<ValidatorHandler<IT, OT>> getValidatorHandlers() throws Exception {
602 if (valHandlers != null) {
605 List<String> handlerClazzes = getServiceBinding().getValidatorHandler();
606 List<ValidatorHandler<IT, OT>> handlers = new ArrayList<ValidatorHandler<IT, OT>>(handlerClazzes.size());
607 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
608 for (String clazz : handlerClazzes) {
609 clazz = clazz.trim();
611 Class<?> c = tccl.loadClass(clazz);
612 if (disableValidationAsserts() == false) {
613 // enable validation assertions
614 tccl.setClassAssertionStatus(clazz, true);
616 if (ValidatorHandler.class.isAssignableFrom(c)) {
617 handlers.add((ValidatorHandler) c.newInstance());
619 } catch (ClassNotFoundException e) {
620 String msg = String.format("Missing document validation handler: '%s'.", clazz);
622 logger.trace(msg, e);
625 valHandlers = handlers;
630 public AuthorityClient getAuthorityClient() throws Exception {
631 AuthorityClient result = authorityClient;
633 if (authorityClient == null) {
634 String authorityClientClazz = getServiceBinding().getClientHandler();
635 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
636 authorityClientClazz = authorityClientClazz.trim();
638 Class<?> c = tccl.loadClass(authorityClientClazz);
639 if (AuthorityClient.class.isAssignableFrom(c)) {
640 result = authorityClient = ((AuthorityClient) c.newInstance());
642 logger.error(String.format("The service binding clientHandler class '%s' for '%s' service was not of type AuthorityClient.",
643 authorityClientClazz, this.getServiceName()));
645 } catch (ClassNotFoundException e) {
646 String msg = String.format("Missing document validation handler: '%s'.", authorityClientClazz);
648 logger.trace(msg, e);
656 public void addValidatorHandler(ValidatorHandler<IT, OT> validator) throws Exception {
657 if (valHandlers == null) {
658 valHandlers = new ArrayList<ValidatorHandler<IT, OT>>();
660 valHandlers.add(validator);
664 * @see java.lang.Object#toString()
667 public String toString() {
668 StringBuilder msg = new StringBuilder();
669 msg.append("AbstractServiceContext [");
670 msg.append("service name=" + serviceBinding.getName() + " ");
671 msg.append("service version=" + serviceBinding.getVersion() + " ");
672 msg.append("tenant id=" + tenantBinding.getId() + " ");
673 msg.append("tenant name=" + tenantBinding.getName() + " ");
674 msg.append(tenantBinding.getDisplayName() + " ");
675 if (repositoryDomain != null) {
676 msg.append("tenant repository domain=" + repositoryDomain.getName());
678 for (Map.Entry<String, Object> entry : properties.entrySet()) {
679 msg.append("property name=" + entry.getKey() + " value=" + entry.getValue().toString());
682 return msg.toString();
686 * @see org.collectionspace.services.common.context.ServiceContext#getQueryParams()
689 public MultivaluedMap<String, String> getQueryParams() {
691 if (queryParams == null){
692 if (this.uriInfo != null){
693 queryParams = this.uriInfo.getQueryParameters();
696 if (queryParams == null){
697 queryParams = new org.jboss.resteasy.specimpl.MultivaluedMapImpl<String,String>();
699 return this.queryParams;
703 public MultivaluedMap<String, String> getQueryParamsPtr() {
704 return this.queryParams;
708 * @see org.collectionspace.services.common.context.ServiceContext#setQueryParams(javax.ws.rs.core.MultivaluedMap)
711 public void setQueryParams(MultivaluedMap<String, String> theQueryParams) {
712 this.queryParams = theQueryParams;
716 public void setUriInfo(UriInfo ui){
721 public UriInfo getUriInfo() {
726 * We expect the 'currentRepositorySession' member to be set only once per instance. Also, we expect only one open repository session
727 * per HTTP request. We'll log an error if we see more than one attempt to set a service context's current repo session.
729 * @see org.collectionspace.services.common.context.ServiceContext#setCurrentRepositorySession(java.lang.Object)
732 public void setCurrentRepositorySession(Object repoSession) throws Exception {
733 if (repoSession == null) {
734 String errMsg = "Setting a service context's repository session to null is not allowed.";
735 logger.error(errMsg);
736 throw new Exception(errMsg);
737 } else if (currentRepositorySession != null && currentRepositorySession != repoSession) {
738 String errMsg = "The current service context's repository session was replaced. This may cause unexpected behavior and/or data loss.";
739 logger.error(errMsg);
740 throw new Exception(errMsg);
743 currentRepositorySession = repoSession;
744 this.currentRepoSesssionRefCount++;
748 public void clearCurrentRepositorySession() {
749 if (this.currentRepoSesssionRefCount > 0) {
750 currentRepoSesssionRefCount--;
753 if (currentRepoSesssionRefCount == 0) {
754 this.currentRepositorySession = null;
759 public Object getCurrentRepositorySession() {
760 // TODO Auto-generated method stub
761 return currentRepositorySession;
765 public RepositoryDomainType getRepositoryDomain() {
766 return repositoryDomain;
770 public void setRepositoryDomain(RepositoryDomainType repositoryDomain) {
771 this.repositoryDomain = repositoryDomain;