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.CollectionSpaceClient;
37 import org.collectionspace.services.client.IClientQueryParams;
38 import org.collectionspace.services.client.IQueryManager;
39 import org.collectionspace.services.client.workflow.WorkflowClient;
40 import org.collectionspace.services.common.ServiceMain;
41 import org.collectionspace.services.common.authorization_mgt.AuthorizationCommon;
42 import org.collectionspace.services.common.config.PropertyItemUtils;
43 import org.collectionspace.services.common.config.ServiceConfigUtils;
44 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
45 import org.collectionspace.services.common.document.DocumentHandler;
46 import org.collectionspace.services.common.document.DocumentFilter;
47 import org.collectionspace.services.common.document.ValidatorHandler;
48 import org.collectionspace.services.common.security.SecurityContext;
49 import org.collectionspace.services.common.security.SecurityContextImpl;
50 import org.collectionspace.services.common.security.UnauthorizedException;
51 import org.collectionspace.services.config.ClientType;
52 import org.collectionspace.services.config.service.ObjectPartType;
53 import org.collectionspace.services.config.service.ServiceBindingType;
54 import org.collectionspace.services.config.tenant.RepositoryDomainType;
55 import org.collectionspace.services.config.tenant.TenantBindingType;
56 import org.collectionspace.services.config.types.PropertyItemType;
57 import org.collectionspace.services.config.types.PropertyType;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 * AbstractServiceContext
64 * $LastChangedRevision: $
79 public abstract class AbstractServiceContextImpl<IT, OT>
80 implements ServiceContext<IT, OT> {
83 final Logger logger = LoggerFactory.getLogger(AbstractServiceContextImpl.class);
85 /** The properties. */
86 Map<String, Object> properties = new HashMap<String, Object>();
87 /** The object part map. */
88 Map<String, ObjectPartType> objectPartMap = new HashMap<String, ObjectPartType>();
89 /** The service binding. */
90 protected ServiceBindingType serviceBinding;
91 /** The tenant binding. */
92 private TenantBindingType tenantBinding;
93 /** repository domain used by the service */
94 private RepositoryDomainType repositoryDomain;
95 /** The override document type. */
96 private String overrideDocumentType = null;
97 /** The val handlers. */
98 private List<ValidatorHandler<IT, OT>> valHandlers = null;
99 /** The authority client -use for shared authority server */
100 private AuthorityClient authorityClient = null;
101 /** The doc handler. */
102 private DocumentHandler docHandler = null;
103 /** security context */
104 private SecurityContext securityContext;
105 /** The sessions JAX-RS URI information */
106 private UriInfo uriInfo;
107 /** The current repository session */
108 private Object currentRepositorySession;
109 /** A reference count for the current repository session */
110 private int currentRepoSesssionRefCount = 0;
113 * Instantiates a new abstract service context impl.
115 private AbstractServiceContextImpl() {
116 // private constructor for singleton pattern
118 // request query params
119 /** The query params. */
120 private MultivaluedMap<String, String> queryParams;
123 * Instantiates a new abstract service context impl.
125 * @param serviceName the service name
127 * @throws UnauthorizedException the unauthorized exception
129 protected AbstractServiceContextImpl(String serviceName, UriInfo uriInfo) throws UnauthorizedException {
131 //establish security context
132 securityContext = new SecurityContextImpl(uriInfo);
133 //make sure tenant context exists
134 checkTenantContext();
136 String tenantId = securityContext.getCurrentTenantId();
137 if (AuthorizationCommon.ALL_TENANTS_MANAGER_TENANT_ID.equals(tenantId) ||
138 AuthNContext.ANONYMOUS_TENANT_ID.equals(tenantId)) {
139 // Tenant Manager has no tenant binding, so don't bother...
140 tenantBinding = null;
141 serviceBinding = null;
142 repositoryDomain = null;
144 //retrieve service bindings
145 TenantBindingConfigReaderImpl tReader =
146 ServiceMain.getInstance().getTenantBindingConfigReader();
147 tenantBinding = tReader.getTenantBinding(tenantId);
148 if (tenantBinding == null) {
149 String msg = "No tenant binding found for tenantId=" + tenantId
150 + " while processing request for service= " + serviceName;
152 throw new IllegalStateException(msg);
154 serviceBinding = tReader.getServiceBinding(tenantId, serviceName);
155 if (serviceBinding == null) {
156 String msg = "No service binding found while processing request for "
157 + serviceName + " for tenant id=" + getTenantId()
158 + " name=" + getTenantName();
160 throw new IllegalStateException(msg);
162 if (logger.isDebugEnabled()) {
163 logger.debug("tenantId=" + tenantId
164 + " service binding=" + serviceBinding.getName());
166 repositoryDomain = tReader.getRepositoryDomain(tenantId, serviceName);
167 if (repositoryDomain != null) {
168 if (logger.isDebugEnabled()) {
169 logger.debug("tenantId=" + tenantId
170 + " repository doamin=" + repositoryDomain.getName());
176 public int getTimeoutParam(UriInfo ui) {
177 int result = DEFAULT_TX_TIMEOUT;
179 MultivaluedMap<String, String> queryParams = (ui == null) ? null : ui.getQueryParameters();
180 if (queryParams != null) {
181 String timeoutString = queryParams.getFirst(IClientQueryParams.IMPORT_TIMEOUT_PARAM);
182 if (timeoutString != null)
184 result = Integer.parseInt(timeoutString);
185 } catch (NumberFormatException e) {
186 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.",
195 public int getTimeoutSecs() {
196 UriInfo uriInfo = this.getUriInfo();
197 return this.getTimeoutParam(uriInfo);
201 * @see org.collectionspace.services.common.context.ServiceContext#getCommonPartLabel()
204 public String getCommonPartLabel() {
205 return getCommonPartLabel(getServiceName());
209 * @see org.collectionspace.services.common.context.ServiceContext#getCommonPartLabel(java.lang.String)
211 public String getCommonPartLabel(String schemaName) {
212 return schemaName.toLowerCase() + PART_LABEL_SEPARATOR + PART_COMMON_LABEL;
216 * @see org.collectionspace.services.common.context.ServiceContext#getPartsMetadata()
219 public Map<String, ObjectPartType> getPartsMetadata() {
220 if (objectPartMap.size() != 0) {
221 return objectPartMap;
223 ServiceBindingUtils.getPartsMetadata(getServiceBinding(), objectPartMap);
224 return objectPartMap;
228 * Gets the properties for part.
230 * @param partLabel the part label
232 * @return the properties for part
234 public List<PropertyItemType> getPropertiesForPart(String partLabel) {
235 Map<String, ObjectPartType> partMap = getPartsMetadata();
236 ObjectPartType part = partMap.get(partLabel);
238 throw new RuntimeException("No such part found: " + partLabel);
240 List<PropertyType> propNodeList = part.getProperties();
241 return propNodeList.isEmpty() ? null : propNodeList.get(0).getItem();
245 * @param partLabel The name of the scehma part to search in
246 * @param propName The name of the property (or properties) to find
247 * @param qualified Whether the returned values should be qualified with the
248 * partLabel. This is when the property values are schema field references.
249 * @return List of property values for the matched property on the named schema part.
251 public List<String> getPropertyValuesForPart(String partLabel, String propName, boolean qualified) {
252 List<PropertyItemType> allProps = getPropertiesForPart(partLabel);
253 return PropertyItemUtils.getPropertyValuesByName(allProps, propName,
254 (qualified ? (partLabel + ":") : null));
258 * @param propName The name of the property (or properties) to find
259 * @param qualified Whether the returned values should be qualified with the
260 * partLabel. This is when the property values are schema field references.
261 * @return List of property values for the matched property on any schema part.
263 public List<String> getAllPartsPropertyValues(String propName, boolean qualified) {
264 return ServiceBindingUtils.getAllPartsPropertyValues(getServiceBinding(), propName, qualified);
268 * @see org.collectionspace.services.common.context.ServiceContext#getServiceBindingPropertyValue(java.lang.String)
270 public String getServiceBindingPropertyValue(String propName) {
271 return ServiceBindingUtils.getPropertyValue(getServiceBinding(), propName);
275 * Gets the common part properties.
277 * @return the common part properties
279 public List<PropertyItemType> getCommonPartProperties() {
280 return getPropertiesForPart(getCommonPartLabel());
284 * @param propName The name of the property (or properties) to find
285 * @param qualified Whether the returned values should be qualified with the
286 * partLabel. This is when the property values are schema field references.
287 * @return List of property values for the matched property on the common schema part.
289 public List<String> getCommonPartPropertyValues(String propName, boolean qualified) {
290 return getPropertyValuesForPart(getCommonPartLabel(), propName, qualified);
294 * @see org.collectionspace.services.common.context.ServiceContext#getQualifiedServiceName()
297 public String getQualifiedServiceName() {
298 return TenantBindingConfigReaderImpl.getTenantQualifiedServiceName(getTenantId(), getServiceName());
302 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryClientName()
305 public String getRepositoryClientName() {
306 if (repositoryDomain == null) {
309 return repositoryDomain.getRepositoryClient();
313 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryClientType()
316 public ClientType getRepositoryClientType() {
317 //assumption: there is only one repository client configured
318 return ServiceMain.getInstance().getClientType();
322 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryDomainName()
325 public String getRepositoryDomainName() {
326 if (repositoryDomain == null) {
329 return repositoryDomain.getName();
333 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryDomainName()
336 public String getRepositoryDomainStorageName() {
337 if (repositoryDomain == null) {
340 return repositoryDomain.getStorageName();
344 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryWorkspaceId()
347 public String getRepositoryWorkspaceId() {
348 return ServiceMain.getInstance().getWorkspaceId(getTenantId(), getServiceName());
352 * @see org.collectionspace.services.common.context.ServiceContext#getRepositoryWorkspaceName()
355 public String getRepositoryWorkspaceName() {
356 //service name is workspace name by convention
357 return serviceBinding.getName();
361 * @see org.collectionspace.services.common.context.ServiceContext#getServiceBinding()
364 public ServiceBindingType getServiceBinding() {
365 return serviceBinding;
369 * @see org.collectionspace.services.common.context.ServiceContext#getServiceName()
372 public String getServiceName() {
373 return serviceBinding.getName();
377 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentType()
380 public String getDocumentType() {
381 // If they have not overridden the setting, use the type of the service
383 return (overrideDocumentType != null) ? overrideDocumentType : serviceBinding.getObject().getName();
387 public String getTenantQualifiedDoctype(String docType) {
388 // If they have not overridden the setting, use the type of the service
390 String result = ServiceBindingUtils.getTenantQualifiedDocType(this.getTenantId(), docType);
396 public String getTenantQualifiedDoctype() {
397 String docType = (overrideDocumentType != null) ? overrideDocumentType : serviceBinding.getObject().getName();
398 return getTenantQualifiedDoctype(docType);
402 * @see org.collectionspace.services.common.context.ServiceContext#setDocumentType(java.lang.String)
405 public void setDocumentType(String docType) {
406 overrideDocumentType = docType;
410 * @see org.collectionspace.services.common.context.ServiceContext#getSecurityContext()
413 public SecurityContext getSecurityContext() {
414 return securityContext;
418 * @see org.collectionspace.services.common.context.ServiceContext#getUserId()
421 public String getUserId() {
422 return securityContext.getUserId();
426 * @see org.collectionspace.services.common.context.ServiceContext#getTenantId()
429 public String getTenantId() {
430 return securityContext.getCurrentTenantId();
434 * @see org.collectionspace.services.common.context.ServiceContext#getTenantName()
437 public String getTenantName() {
438 return securityContext.getCurrentTenantName();
442 * @see org.collectionspace.services.common.context.ServiceContext#getInput()
445 public abstract IT getInput();
448 * @see org.collectionspace.services.common.context.ServiceContext#setInput(java.lang.Object)
451 public abstract void setInput(IT input);
454 * @see org.collectionspace.services.common.context.ServiceContext#getOutput()
457 public abstract OT getOutput();
460 * @see org.collectionspace.services.common.context.ServiceContext#setOutput(java.lang.Object)
463 public abstract void setOutput(OT output);
466 * @see org.collectionspace.services.common.context.ServiceContext#getProperties()
469 public Map<String, Object> getProperties() {
474 * @see org.collectionspace.services.common.context.ServiceContext#setProperties(java.util.Map)
477 public void setProperties(Map<String, Object> props) {
478 properties.putAll(props);
482 * @see org.collectionspace.services.common.context.ServiceContext#getProperty(java.lang.String)
484 public Object getProperty(String name) {
485 return properties.get(name);
489 * @see org.collectionspace.services.common.context.ServiceContext#setProperty(java.lang.String, java.lang.Object)
491 public void setProperty(String name, Object o) {
492 properties.put(name, o);
496 * checkTenantContext makss sure tenant context exists
500 * @throws UnauthorizedException the unauthorized exception
502 private void checkTenantContext() throws UnauthorizedException {
504 String tenantId = securityContext.getCurrentTenantId();
505 if (tenantId == null) {
506 String msg = "Could not find tenant context";
508 throw new UnauthorizedException(msg);
513 * Helps to filter for queries that either want to include or exclude documents in deleted workflow states.
518 private static String buildWorkflowWhereClause(MultivaluedMap<String, String> queryParams) {
519 String result = null;
521 String includeDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_NONDELETED);
522 String includeOnlyDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_ONLY_DELETED);
524 if (includeDeleted != null && includeDeleted.equalsIgnoreCase(Boolean.FALSE.toString())) {
525 result = String.format("(ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s')",
526 WorkflowClient.WORKFLOWSTATE_DELETED, WorkflowClient.WORKFLOWSTATE_LOCKED_DELETED, WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED);
527 } else if (includeOnlyDeleted != null && includeOnlyDeleted.equalsIgnoreCase(Boolean.TRUE.toString())) {
528 result = String.format("(ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s')",
529 WorkflowClient.WORKFLOWSTATE_PROJECT, WorkflowClient.WORKFLOWSTATE_LOCKED, WorkflowClient.WORKFLOWSTATE_REPLICATED);
536 * Creates the document handler instance.
538 * @return the document handler
540 * @throws Exception the exception
542 private DocumentHandler createDocumentHandlerInstance() throws Exception {
543 docHandler = ServiceConfigUtils.createDocumentHandlerInstance(tenantBinding, serviceBinding);
545 // Create a default document filter
547 docHandler.setServiceContext(this);
548 DocumentFilter docFilter = docHandler.createDocumentFilter();
550 // If the context was created with query parameters,
551 // reflect the values of those parameters in the document filter
552 // to specify sort ordering, pagination, etc.
554 MultivaluedMap<String, String> queryParameters = this.getQueryParams();
555 if (queryParameters != null) {
556 docFilter.setSortOrder(queryParameters);
557 docFilter.setPagination(queryParameters);
558 String workflowWhereClause = buildWorkflowWhereClause(queryParameters);
559 if (workflowWhereClause != null) {
560 docFilter.appendWhereClause(workflowWhereClause, IQueryManager.SEARCH_QUALIFIER_AND);
564 docHandler.setDocumentFilter(docFilter);
570 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHandler()
573 public DocumentHandler getDocumentHandler() throws Exception {
574 DocumentHandler result = docHandler;
575 // create a new instance if one does not yet exist
576 if (result == null) {
577 result = createDocumentHandlerInstance();
583 public void setDocumentHandler(DocumentHandler handler) throws Exception {
584 if (handler != null) {
585 docHandler = handler;
590 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHanlder(javax.ws.rs.core.MultivaluedMap)
593 public DocumentHandler getDocumentHandler(MultivaluedMap<String, String> queryParams) throws Exception {
594 DocumentHandler result = getDocumentHandler();
595 DocumentFilter documentFilter = result.getDocumentFilter(); //to see results in debugger variables view
596 documentFilter.setPagination(queryParams);
601 * If this element is set in the service binding then use it otherwise
602 * assume that asserts are NOT disabled.
604 private boolean disableValidationAsserts() {
606 Boolean disableAsserts = getServiceBinding().isDisableAsserts();
607 result = (disableAsserts != null) ? disableAsserts : false;
612 * @see org.collectionspace.services.common.context.ServiceContext#getValidatorHandlers()
615 public List<ValidatorHandler<IT, OT>> getValidatorHandlers() throws Exception {
616 if (valHandlers != null) {
619 List<String> handlerClazzes = getServiceBinding().getValidatorHandler();
620 List<ValidatorHandler<IT, OT>> handlers = new ArrayList<ValidatorHandler<IT, OT>>(handlerClazzes.size());
621 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
622 for (String clazz : handlerClazzes) {
623 clazz = clazz.trim();
625 Class<?> c = tccl.loadClass(clazz);
626 if (disableValidationAsserts() == false) {
627 // enable validation assertions
628 tccl.setClassAssertionStatus(clazz, true);
630 if (ValidatorHandler.class.isAssignableFrom(c)) {
631 handlers.add((ValidatorHandler) c.newInstance());
633 } catch (ClassNotFoundException e) {
634 String msg = String.format("Missing document validation handler: '%s'.", clazz);
636 logger.trace(msg, e);
639 valHandlers = handlers;
644 * If one doesn't already exist, use the default properties filename to load a set of properties that
645 * will be used to create an HTTP client to a CollectionSpace instance.
648 public AuthorityClient getClient() throws Exception {
649 AuthorityClient result = authorityClient;
651 if (authorityClient == null) {
652 result = authorityClient = getClient(CollectionSpaceClient.DEFAULT_CLIENT_PROPERTIES_FILENAME);
659 * Use the properties filename passed in to load the URL and credentials that will be used
660 * to create a new HTTP client.
662 * Never uses or resets the this.authorityClient member. Always creates a new HTTP client using
663 * the loaded properties.
666 * @see org.collectionspace.services.common.context.ServiceContext#getClient(java.lang.String)
669 public AuthorityClient getClient(String clientPropertiesFilename) throws Exception {
670 AuthorityClient result = null;
672 String authorityClientClazz = getServiceBinding().getClientHandler();
673 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
674 authorityClientClazz = authorityClientClazz.trim();
676 Class<?> c = tccl.loadClass(authorityClientClazz);
677 if (AuthorityClient.class.isAssignableFrom(c)) {
678 result = authorityClient = ((AuthorityClient) c.newInstance());
679 result.setClientProperties(clientPropertiesFilename);
681 logger.error(String.format("The service binding clientHandler class '%s' for '%s' service was not of type AuthorityClient.",
682 authorityClientClazz, this.getServiceName()));
684 } catch (ClassNotFoundException e) {
685 String msg = String.format("Missing document validation handler: '%s'.", authorityClientClazz);
687 logger.trace(msg, e);
694 public void addValidatorHandler(ValidatorHandler<IT, OT> validator) throws Exception {
695 if (valHandlers == null) {
696 valHandlers = new ArrayList<ValidatorHandler<IT, OT>>();
698 valHandlers.add(validator);
702 * @see java.lang.Object#toString()
705 public String toString() {
706 StringBuilder msg = new StringBuilder();
707 msg.append("AbstractServiceContext [");
708 msg.append("service name=" + serviceBinding.getName() + " ");
709 msg.append("service version=" + serviceBinding.getVersion() + " ");
710 msg.append("tenant id=" + tenantBinding.getId() + " ");
711 msg.append("tenant name=" + tenantBinding.getName() + " ");
712 msg.append(tenantBinding.getDisplayName() + " ");
713 if (repositoryDomain != null) {
714 msg.append("tenant repository domain=" + repositoryDomain.getName());
716 for (Map.Entry<String, Object> entry : properties.entrySet()) {
717 msg.append("property name=" + entry.getKey() + " value=" + entry.getValue().toString());
720 return msg.toString();
724 * @see org.collectionspace.services.common.context.ServiceContext#getQueryParams()
727 public MultivaluedMap<String, String> getQueryParams() {
729 if (queryParams == null){
730 if (this.uriInfo != null){
731 queryParams = this.uriInfo.getQueryParameters();
734 if (queryParams == null){
735 queryParams = new org.jboss.resteasy.specimpl.MultivaluedMapImpl<String,String>();
737 return this.queryParams;
741 public MultivaluedMap<String, String> getQueryParamsPtr() {
742 return this.queryParams;
746 * @see org.collectionspace.services.common.context.ServiceContext#setQueryParams(javax.ws.rs.core.MultivaluedMap)
749 public void setQueryParams(MultivaluedMap<String, String> theQueryParams) {
750 this.queryParams = theQueryParams;
754 public void setUriInfo(UriInfo ui){
759 public UriInfo getUriInfo() {
764 * We expect the 'currentRepositorySession' member to be set only once per instance. Also, we expect only one open repository session
765 * per HTTP request. We'll log an error if we see more than one attempt to set a service context's current repo session.
767 * @see org.collectionspace.services.common.context.ServiceContext#setCurrentRepositorySession(java.lang.Object)
770 public void setCurrentRepositorySession(Object repoSession) throws Exception {
771 if (repoSession == null) {
772 String errMsg = "Setting a service context's repository session to null is not allowed.";
773 logger.error(errMsg);
774 throw new Exception(errMsg);
775 } else if (currentRepositorySession != null && currentRepositorySession != repoSession) {
776 String errMsg = "The current service context's repository session was replaced. This may cause unexpected behavior and/or data loss.";
777 logger.error(errMsg);
778 throw new Exception(errMsg);
781 currentRepositorySession = repoSession;
782 this.currentRepoSesssionRefCount++;
786 public void clearCurrentRepositorySession() {
787 if (this.currentRepoSesssionRefCount > 0) {
788 currentRepoSesssionRefCount--;
791 if (currentRepoSesssionRefCount == 0) {
792 this.currentRepositorySession = null;
797 public Object getCurrentRepositorySession() {
798 // TODO Auto-generated method stub
799 return currentRepositorySession;
803 public RepositoryDomainType getRepositoryDomain() {
804 return repositoryDomain;
808 public void setRepositoryDomain(RepositoryDomainType repositoryDomain) {
809 this.repositoryDomain = repositoryDomain;