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 String includeOnlyDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_ONLY_DELETED);
517 if (includeDeleted != null && includeDeleted.equalsIgnoreCase(Boolean.FALSE.toString())) {
518 result = String.format("(ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s')",
519 WorkflowClient.WORKFLOWSTATE_DELETED, WorkflowClient.WORKFLOWSTATE_LOCKED_DELETED);
520 } else if (includeOnlyDeleted != null && includeOnlyDeleted.equalsIgnoreCase(Boolean.TRUE.toString())) {
521 result = String.format("(ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s')",
522 WorkflowClient.WORKFLOWSTATE_PROJECT, WorkflowClient.WORKFLOWSTATE_LOCKED);
529 * Creates the document handler instance.
531 * @return the document handler
533 * @throws Exception the exception
535 private DocumentHandler createDocumentHandlerInstance() throws Exception {
536 docHandler = ServiceConfigUtils.createDocumentHandlerInstance(tenantBinding, serviceBinding);
538 // Create a default document filter
540 docHandler.setServiceContext(this);
541 DocumentFilter docFilter = docHandler.createDocumentFilter();
543 // If the context was created with query parameters,
544 // reflect the values of those parameters in the document filter
545 // to specify sort ordering, pagination, etc.
547 MultivaluedMap<String, String> queryParameters = this.getQueryParams();
548 if (queryParameters != null) {
549 docFilter.setSortOrder(queryParameters);
550 docFilter.setPagination(queryParameters);
551 String workflowWhereClause = buildWorkflowWhereClause(queryParameters);
552 if (workflowWhereClause != null) {
553 docFilter.appendWhereClause(workflowWhereClause, IQueryManager.SEARCH_QUALIFIER_AND);
557 docHandler.setDocumentFilter(docFilter);
563 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHandler()
566 public DocumentHandler getDocumentHandler() throws Exception {
567 DocumentHandler result = docHandler;
568 // create a new instance if one does not yet exist
569 if (result == null) {
570 result = createDocumentHandlerInstance();
576 public void setDocumentHandler(DocumentHandler handler) throws Exception {
577 if (handler != null) {
578 docHandler = handler;
583 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHanlder(javax.ws.rs.core.MultivaluedMap)
586 public DocumentHandler getDocumentHandler(MultivaluedMap<String, String> queryParams) throws Exception {
587 DocumentHandler result = getDocumentHandler();
588 DocumentFilter documentFilter = result.getDocumentFilter(); //to see results in debugger variables view
589 documentFilter.setPagination(queryParams);
594 * If this element is set in the service binding then use it otherwise
595 * assume that asserts are NOT disabled.
597 private boolean disableValidationAsserts() {
599 Boolean disableAsserts = getServiceBinding().isDisableAsserts();
600 result = (disableAsserts != null) ? disableAsserts : false;
605 * @see org.collectionspace.services.common.context.ServiceContext#getValidatorHandlers()
608 public List<ValidatorHandler<IT, OT>> getValidatorHandlers() throws Exception {
609 if (valHandlers != null) {
612 List<String> handlerClazzes = getServiceBinding().getValidatorHandler();
613 List<ValidatorHandler<IT, OT>> handlers = new ArrayList<ValidatorHandler<IT, OT>>(handlerClazzes.size());
614 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
615 for (String clazz : handlerClazzes) {
616 clazz = clazz.trim();
618 Class<?> c = tccl.loadClass(clazz);
619 if (disableValidationAsserts() == false) {
620 // enable validation assertions
621 tccl.setClassAssertionStatus(clazz, true);
623 if (ValidatorHandler.class.isAssignableFrom(c)) {
624 handlers.add((ValidatorHandler) c.newInstance());
626 } catch (ClassNotFoundException e) {
627 String msg = String.format("Missing document validation handler: '%s'.", clazz);
629 logger.trace(msg, e);
632 valHandlers = handlers;
637 public AuthorityClient getClient() throws Exception {
638 AuthorityClient result = authorityClient;
640 if (authorityClient == null) {
641 String authorityClientClazz = getServiceBinding().getClientHandler();
642 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
643 authorityClientClazz = authorityClientClazz.trim();
645 Class<?> c = tccl.loadClass(authorityClientClazz);
646 if (AuthorityClient.class.isAssignableFrom(c)) {
647 result = authorityClient = ((AuthorityClient) c.newInstance());
649 logger.error(String.format("The service binding clientHandler class '%s' for '%s' service was not of type AuthorityClient.",
650 authorityClientClazz, this.getServiceName()));
652 } catch (ClassNotFoundException e) {
653 String msg = String.format("Missing document validation handler: '%s'.", authorityClientClazz);
655 logger.trace(msg, e);
663 public void addValidatorHandler(ValidatorHandler<IT, OT> validator) throws Exception {
664 if (valHandlers == null) {
665 valHandlers = new ArrayList<ValidatorHandler<IT, OT>>();
667 valHandlers.add(validator);
671 * @see java.lang.Object#toString()
674 public String toString() {
675 StringBuilder msg = new StringBuilder();
676 msg.append("AbstractServiceContext [");
677 msg.append("service name=" + serviceBinding.getName() + " ");
678 msg.append("service version=" + serviceBinding.getVersion() + " ");
679 msg.append("tenant id=" + tenantBinding.getId() + " ");
680 msg.append("tenant name=" + tenantBinding.getName() + " ");
681 msg.append(tenantBinding.getDisplayName() + " ");
682 if (repositoryDomain != null) {
683 msg.append("tenant repository domain=" + repositoryDomain.getName());
685 for (Map.Entry<String, Object> entry : properties.entrySet()) {
686 msg.append("property name=" + entry.getKey() + " value=" + entry.getValue().toString());
689 return msg.toString();
693 * @see org.collectionspace.services.common.context.ServiceContext#getQueryParams()
696 public MultivaluedMap<String, String> getQueryParams() {
698 if (queryParams == null){
699 if (this.uriInfo != null){
700 queryParams = this.uriInfo.getQueryParameters();
703 if (queryParams == null){
704 queryParams = new org.jboss.resteasy.specimpl.MultivaluedMapImpl<String,String>();
706 return this.queryParams;
710 public MultivaluedMap<String, String> getQueryParamsPtr() {
711 return this.queryParams;
715 * @see org.collectionspace.services.common.context.ServiceContext#setQueryParams(javax.ws.rs.core.MultivaluedMap)
718 public void setQueryParams(MultivaluedMap<String, String> theQueryParams) {
719 this.queryParams = theQueryParams;
723 public void setUriInfo(UriInfo ui){
728 public UriInfo getUriInfo() {
733 * We expect the 'currentRepositorySession' member to be set only once per instance. Also, we expect only one open repository session
734 * per HTTP request. We'll log an error if we see more than one attempt to set a service context's current repo session.
736 * @see org.collectionspace.services.common.context.ServiceContext#setCurrentRepositorySession(java.lang.Object)
739 public void setCurrentRepositorySession(Object repoSession) throws Exception {
740 if (repoSession == null) {
741 String errMsg = "Setting a service context's repository session to null is not allowed.";
742 logger.error(errMsg);
743 throw new Exception(errMsg);
744 } else if (currentRepositorySession != null && currentRepositorySession != repoSession) {
745 String errMsg = "The current service context's repository session was replaced. This may cause unexpected behavior and/or data loss.";
746 logger.error(errMsg);
747 throw new Exception(errMsg);
750 currentRepositorySession = repoSession;
751 this.currentRepoSesssionRefCount++;
755 public void clearCurrentRepositorySession() {
756 if (this.currentRepoSesssionRefCount > 0) {
757 currentRepoSesssionRefCount--;
760 if (currentRepoSesssionRefCount == 0) {
761 this.currentRepositorySession = null;
766 public Object getCurrentRepositorySession() {
767 // TODO Auto-generated method stub
768 return currentRepositorySession;
772 public RepositoryDomainType getRepositoryDomain() {
773 return repositoryDomain;
777 public void setRepositoryDomain(RepositoryDomainType repositoryDomain) {
778 this.repositoryDomain = repositoryDomain;