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);
512 private static String buildWorkflowWhereClause(MultivaluedMap<String, String> queryParams) {
513 String result = null;
515 String includeDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_NONDELETED);
516 String includeOnlyDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_ONLY_DELETED);
518 if (includeDeleted != null && includeDeleted.equalsIgnoreCase(Boolean.FALSE.toString())) {
519 result = String.format("(ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s')",
520 WorkflowClient.WORKFLOWSTATE_DELETED, WorkflowClient.WORKFLOWSTATE_LOCKED_DELETED);
521 } else if (includeOnlyDeleted != null && includeOnlyDeleted.equalsIgnoreCase(Boolean.TRUE.toString())) {
522 result = String.format("(ecm:currentLifeCycleState <> '%s' AND ecm:currentLifeCycleState <> '%s')",
523 WorkflowClient.WORKFLOWSTATE_PROJECT, WorkflowClient.WORKFLOWSTATE_LOCKED);
530 * Creates the document handler instance.
532 * @return the document handler
534 * @throws Exception the exception
536 private DocumentHandler createDocumentHandlerInstance() throws Exception {
537 docHandler = ServiceConfigUtils.createDocumentHandlerInstance(tenantBinding, serviceBinding);
539 // Create a default document filter
541 docHandler.setServiceContext(this);
542 DocumentFilter docFilter = docHandler.createDocumentFilter();
544 // If the context was created with query parameters,
545 // reflect the values of those parameters in the document filter
546 // to specify sort ordering, pagination, etc.
548 MultivaluedMap<String, String> queryParameters = this.getQueryParams();
549 if (queryParameters != null) {
550 docFilter.setSortOrder(queryParameters);
551 docFilter.setPagination(queryParameters);
552 String workflowWhereClause = buildWorkflowWhereClause(queryParameters);
553 if (workflowWhereClause != null) {
554 docFilter.appendWhereClause(workflowWhereClause, IQueryManager.SEARCH_QUALIFIER_AND);
558 docHandler.setDocumentFilter(docFilter);
564 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHandler()
567 public DocumentHandler getDocumentHandler() throws Exception {
568 DocumentHandler result = docHandler;
569 // create a new instance if one does not yet exist
570 if (result == null) {
571 result = createDocumentHandlerInstance();
577 public void setDocumentHandler(DocumentHandler handler) throws Exception {
578 if (handler != null) {
579 docHandler = handler;
584 * @see org.collectionspace.services.common.context.ServiceContext#getDocumentHanlder(javax.ws.rs.core.MultivaluedMap)
587 public DocumentHandler getDocumentHandler(MultivaluedMap<String, String> queryParams) throws Exception {
588 DocumentHandler result = getDocumentHandler();
589 DocumentFilter documentFilter = result.getDocumentFilter(); //to see results in debugger variables view
590 documentFilter.setPagination(queryParams);
595 * If this element is set in the service binding then use it otherwise
596 * assume that asserts are NOT disabled.
598 private boolean disableValidationAsserts() {
600 Boolean disableAsserts = getServiceBinding().isDisableAsserts();
601 result = (disableAsserts != null) ? disableAsserts : false;
606 * @see org.collectionspace.services.common.context.ServiceContext#getValidatorHandlers()
609 public List<ValidatorHandler<IT, OT>> getValidatorHandlers() throws Exception {
610 if (valHandlers != null) {
613 List<String> handlerClazzes = getServiceBinding().getValidatorHandler();
614 List<ValidatorHandler<IT, OT>> handlers = new ArrayList<ValidatorHandler<IT, OT>>(handlerClazzes.size());
615 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
616 for (String clazz : handlerClazzes) {
617 clazz = clazz.trim();
619 Class<?> c = tccl.loadClass(clazz);
620 if (disableValidationAsserts() == false) {
621 // enable validation assertions
622 tccl.setClassAssertionStatus(clazz, true);
624 if (ValidatorHandler.class.isAssignableFrom(c)) {
625 handlers.add((ValidatorHandler) c.newInstance());
627 } catch (ClassNotFoundException e) {
628 String msg = String.format("Missing document validation handler: '%s'.", clazz);
630 logger.trace(msg, e);
633 valHandlers = handlers;
638 * If one doesn't already exist, use the default properties filename to load a set of properties that
639 * will be used to create an HTTP client to a CollectionSpace instance.
642 public AuthorityClient getClient() throws Exception {
643 AuthorityClient result = authorityClient;
645 if (authorityClient == null) {
646 result = authorityClient = getClient(CollectionSpaceClient.DEFAULT_CLIENT_PROPERTIES_FILENAME);
653 * Use the properties filename passed in to load the URL and credentials that will be used
654 * to create a new HTTP client.
656 * Never uses or resets the this.authorityClient member. Always creates a new HTTP client using
657 * the loaded properties.
660 * @see org.collectionspace.services.common.context.ServiceContext#getClient(java.lang.String)
663 public AuthorityClient getClient(String clientPropertiesFilename) throws Exception {
664 AuthorityClient result = null;
666 String authorityClientClazz = getServiceBinding().getClientHandler();
667 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
668 authorityClientClazz = authorityClientClazz.trim();
670 Class<?> c = tccl.loadClass(authorityClientClazz);
671 if (AuthorityClient.class.isAssignableFrom(c)) {
672 result = authorityClient = ((AuthorityClient) c.newInstance());
673 result.setClientProperties(clientPropertiesFilename);
675 logger.error(String.format("The service binding clientHandler class '%s' for '%s' service was not of type AuthorityClient.",
676 authorityClientClazz, this.getServiceName()));
678 } catch (ClassNotFoundException e) {
679 String msg = String.format("Missing document validation handler: '%s'.", authorityClientClazz);
681 logger.trace(msg, e);
688 public void addValidatorHandler(ValidatorHandler<IT, OT> validator) throws Exception {
689 if (valHandlers == null) {
690 valHandlers = new ArrayList<ValidatorHandler<IT, OT>>();
692 valHandlers.add(validator);
696 * @see java.lang.Object#toString()
699 public String toString() {
700 StringBuilder msg = new StringBuilder();
701 msg.append("AbstractServiceContext [");
702 msg.append("service name=" + serviceBinding.getName() + " ");
703 msg.append("service version=" + serviceBinding.getVersion() + " ");
704 msg.append("tenant id=" + tenantBinding.getId() + " ");
705 msg.append("tenant name=" + tenantBinding.getName() + " ");
706 msg.append(tenantBinding.getDisplayName() + " ");
707 if (repositoryDomain != null) {
708 msg.append("tenant repository domain=" + repositoryDomain.getName());
710 for (Map.Entry<String, Object> entry : properties.entrySet()) {
711 msg.append("property name=" + entry.getKey() + " value=" + entry.getValue().toString());
714 return msg.toString();
718 * @see org.collectionspace.services.common.context.ServiceContext#getQueryParams()
721 public MultivaluedMap<String, String> getQueryParams() {
723 if (queryParams == null){
724 if (this.uriInfo != null){
725 queryParams = this.uriInfo.getQueryParameters();
728 if (queryParams == null){
729 queryParams = new org.jboss.resteasy.specimpl.MultivaluedMapImpl<String,String>();
731 return this.queryParams;
735 public MultivaluedMap<String, String> getQueryParamsPtr() {
736 return this.queryParams;
740 * @see org.collectionspace.services.common.context.ServiceContext#setQueryParams(javax.ws.rs.core.MultivaluedMap)
743 public void setQueryParams(MultivaluedMap<String, String> theQueryParams) {
744 this.queryParams = theQueryParams;
748 public void setUriInfo(UriInfo ui){
753 public UriInfo getUriInfo() {
758 * We expect the 'currentRepositorySession' member to be set only once per instance. Also, we expect only one open repository session
759 * per HTTP request. We'll log an error if we see more than one attempt to set a service context's current repo session.
761 * @see org.collectionspace.services.common.context.ServiceContext#setCurrentRepositorySession(java.lang.Object)
764 public void setCurrentRepositorySession(Object repoSession) throws Exception {
765 if (repoSession == null) {
766 String errMsg = "Setting a service context's repository session to null is not allowed.";
767 logger.error(errMsg);
768 throw new Exception(errMsg);
769 } else if (currentRepositorySession != null && currentRepositorySession != repoSession) {
770 String errMsg = "The current service context's repository session was replaced. This may cause unexpected behavior and/or data loss.";
771 logger.error(errMsg);
772 throw new Exception(errMsg);
775 currentRepositorySession = repoSession;
776 this.currentRepoSesssionRefCount++;
780 public void clearCurrentRepositorySession() {
781 if (this.currentRepoSesssionRefCount > 0) {
782 currentRepoSesssionRefCount--;
785 if (currentRepoSesssionRefCount == 0) {
786 this.currentRepositorySession = null;
791 public Object getCurrentRepositorySession() {
792 // TODO Auto-generated method stub
793 return currentRepositorySession;
797 public RepositoryDomainType getRepositoryDomain() {
798 return repositoryDomain;
802 public void setRepositoryDomain(RepositoryDomainType repositoryDomain) {
803 this.repositoryDomain = repositoryDomain;