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.nuxeo.client.java;
26 import java.util.Collection;
27 import java.util.List;
29 import javax.ws.rs.core.MultivaluedMap;
31 import org.collectionspace.services.client.CollectionSpaceClient;
32 import org.collectionspace.services.client.IQueryManager;
33 import org.collectionspace.services.client.PoxPayloadIn;
34 import org.collectionspace.services.client.PoxPayloadOut;
35 import org.collectionspace.services.client.RelationClient;
36 import org.collectionspace.services.common.authorityref.AuthorityRefList;
37 import org.collectionspace.services.common.context.ServiceContext;
38 import org.collectionspace.services.common.datetime.GregorianCalendarDateTimeUtils;
39 import org.collectionspace.services.common.document.AbstractMultipartDocumentHandlerImpl;
40 import org.collectionspace.services.common.document.DocumentFilter;
41 import org.collectionspace.services.common.document.DocumentWrapper;
42 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
43 import org.collectionspace.services.common.profile.Profiler;
44 import org.collectionspace.services.common.repository.RepositoryClient;
45 import org.collectionspace.services.common.repository.RepositoryClientFactory;
46 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
47 import org.collectionspace.services.lifecycle.Lifecycle;
48 import org.collectionspace.services.lifecycle.State;
49 import org.collectionspace.services.lifecycle.StateList;
50 import org.collectionspace.services.lifecycle.TransitionDef;
51 import org.collectionspace.services.lifecycle.TransitionDefList;
52 import org.collectionspace.services.lifecycle.TransitionList;
54 import org.nuxeo.ecm.core.NXCore;
55 import org.nuxeo.ecm.core.api.ClientException;
56 import org.nuxeo.ecm.core.api.DocumentModel;
57 import org.nuxeo.ecm.core.api.DocumentModelList;
58 import org.nuxeo.ecm.core.api.model.PropertyException;
59 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
60 import org.nuxeo.ecm.core.lifecycle.LifeCycleService;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * DocumentModelHandler is a base abstract Nuxeo document handler
67 * using Nuxeo Java Remote APIs for CollectionSpace services
69 * $LastChangedRevision: $
72 public abstract class DocumentModelHandler<T, TL>
73 extends AbstractMultipartDocumentHandlerImpl<T, TL, DocumentModel, DocumentModelList> {
75 private final Logger logger = LoggerFactory.getLogger(DocumentModelHandler.class);
76 private RepositoryInstance repositorySession;
79 * Map Nuxeo's life cycle object to our JAX-B based life cycle object
81 private Lifecycle createCollectionSpaceLifecycle(org.nuxeo.ecm.core.lifecycle.LifeCycle nuxeoLifecyle) {
82 Lifecycle result = null;
84 if (nuxeoLifecyle != null) {
86 // Copy the life cycle's name
87 result = new Lifecycle();
88 result.setName(nuxeoLifecyle.getName());
90 // We currently support only one initial state, so take the first one from Nuxeo
91 Collection<String> initialStateNames = nuxeoLifecyle.getInitialStateNames();
92 result.setDefaultInitial(initialStateNames.iterator().next());
94 // Next, we copy the state and corresponding transition lists
95 StateList stateList = new StateList();
96 List<State> states = stateList.getState();
97 Collection<org.nuxeo.ecm.core.lifecycle.LifeCycleState> nuxeoStates = nuxeoLifecyle.getStates();
98 for (org.nuxeo.ecm.core.lifecycle.LifeCycleState nuxeoState : nuxeoStates) {
99 State tempState = new State();
100 tempState.setDescription(nuxeoState.getDescription());
101 tempState.setInitial(nuxeoState.isInitial());
102 tempState.setName(nuxeoState.getName());
103 // Now get the list of transitions
104 TransitionList transitionList = new TransitionList();
105 List<String> transitions = transitionList.getTransition();
106 Collection<String> nuxeoTransitions = nuxeoState.getAllowedStateTransitions();
107 for (String nuxeoTransition : nuxeoTransitions) {
108 transitions.add(nuxeoTransition);
110 tempState.setTransitionList(transitionList);
111 states.add(tempState);
113 result.setStateList(stateList);
115 // Finally, we create the transition definitions
116 TransitionDefList transitionDefList = new TransitionDefList();
117 List<TransitionDef> transitionDefs = transitionDefList.getTransitionDef();
118 Collection<org.nuxeo.ecm.core.lifecycle.LifeCycleTransition> nuxeoTransitionDefs = nuxeoLifecyle.getTransitions();
119 for (org.nuxeo.ecm.core.lifecycle.LifeCycleTransition nuxeoTransitionDef : nuxeoTransitionDefs) {
120 TransitionDef tempTransitionDef = new TransitionDef();
121 tempTransitionDef.setDescription(nuxeoTransitionDef.getDescription());
122 tempTransitionDef.setDestinationState(nuxeoTransitionDef.getDestinationStateName());
123 tempTransitionDef.setName(nuxeoTransitionDef.getName());
124 transitionDefs.add(tempTransitionDef);
126 result.setTransitionDefList(transitionDefList);
133 * Returns the the life cycle definition of the related Nuxeo document type for this handler.
135 * @see org.collectionspace.services.common.document.DocumentHandler#getLifecycle()
138 public Lifecycle getLifecycle() {
139 Lifecycle result = null;
141 String docTypeName = null;
143 docTypeName = this.getServiceContext().getDocumentType();
144 result = getLifecycle(docTypeName);
145 } catch (Exception e) {
146 if (logger.isTraceEnabled() == true) {
147 logger.trace("Could not retrieve lifecycle definition for Nuxeo doctype: " + docTypeName);
155 * Returns the the life cycle definition of the related Nuxeo document type for this handler.
157 * @see org.collectionspace.services.common.document.DocumentHandler#getLifecycle(java.lang.String)
160 public Lifecycle getLifecycle(String docTypeName) {
161 org.nuxeo.ecm.core.lifecycle.LifeCycle nuxeoLifecyle;
162 Lifecycle result = null;
165 LifeCycleService lifeCycleService = null;
167 lifeCycleService = NXCore.getLifeCycleService();
168 } catch (Exception e) {
172 String lifeCycleName;
173 lifeCycleName = lifeCycleService.getLifeCycleNameFor(docTypeName);
174 nuxeoLifecyle = lifeCycleService.getLifeCycleByName(lifeCycleName);
176 result = createCollectionSpaceLifecycle(nuxeoLifecyle);
177 // result = (Lifecycle)FileTools.getJaxbObjectFromFile(Lifecycle.class, "default-lifecycle.xml");
178 } catch (Exception e) {
179 // TODO Auto-generated catch block
180 logger.error("Could not retreive life cycle information for Nuxeo doctype: " + docTypeName, e);
187 * We're using the "name" field of Nuxeo's DocumentModel to store
190 public String getCsid(DocumentModel docModel) {
191 return NuxeoUtils.getCsid(docModel);
194 public String getUri(DocumentModel docModel) {
195 return getServiceContextPath()+getCsid(docModel);
198 public RepositoryClient<PoxPayloadIn, PoxPayloadOut> getRepositoryClient(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
199 RepositoryClient<PoxPayloadIn, PoxPayloadOut> repositoryClient = RepositoryClientFactory.getInstance().getClient(ctx.getRepositoryClientName());
200 return repositoryClient;
204 * getRepositorySession returns Nuxeo Repository Session
207 public RepositoryInstance getRepositorySession() {
209 return repositorySession;
213 * setRepositorySession sets repository session
216 public void setRepositorySession(RepositoryInstance repoSession) {
217 this.repositorySession = repoSession;
221 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
222 // TODO for sub-docs - check to see if the current service context is a multipart input,
223 // OR a docfragment, and call a variant to fill the DocModel.
224 fillAllParts(wrapDoc, Action.CREATE);
225 handleCoreValues(wrapDoc, Action.CREATE);
228 // TODO for sub-docs - Add completeCreate in which we look for set-aside doc fragments
229 // and create the subitems. We will create service contexts with the doc fragments
234 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
235 // TODO for sub-docs - check to see if the current service context is a multipart input,
236 // OR a docfragment, and call a variant to fill the DocModel.
237 fillAllParts(wrapDoc, Action.UPDATE);
238 handleCoreValues(wrapDoc, Action.UPDATE);
242 public void handleGet(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
243 extractAllParts(wrapDoc);
247 public void handleGetAll(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
248 Profiler profiler = new Profiler(this, 2);
250 setCommonPartList(extractCommonPartList(wrapDoc));
255 public abstract void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
258 public abstract void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
261 public abstract T extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
264 public abstract void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception;
267 public abstract void fillCommonPart(T obj, DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
270 public abstract TL extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception;
273 public abstract T getCommonPart();
276 public abstract void setCommonPart(T obj);
279 public abstract TL getCommonPartList();
282 public abstract void setCommonPartList(TL obj);
285 public DocumentFilter createDocumentFilter() {
286 DocumentFilter filter = new NuxeoDocumentFilter(this.getServiceContext());
291 * Gets the authority refs.
293 * @param docWrapper the doc wrapper
294 * @param authRefFields the auth ref fields
295 * @return the authority refs
296 * @throws PropertyException the property exception
298 abstract public AuthorityRefList getAuthorityRefs(String csid,
299 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException;
301 private void handleCoreValues(DocumentWrapper<DocumentModel> docWrapper,
302 Action action) throws ClientException {
303 DocumentModel documentModel = docWrapper.getWrappedObject();
304 String now = GregorianCalendarDateTimeUtils.timestampUTC();
305 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
306 String userId = ctx.getUserId();
307 if(action==Action.CREATE) {
309 // Add the tenant ID value to the new entity
311 String tenantId = ctx.getTenantId();
312 documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
313 CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID, tenantId);
315 // Add the uri value to the new entity
317 documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
318 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI, getUri(documentModel));
320 // Add the CSID to the DublinCore title so we can see the CSID in the default
324 documentModel.setProperty("dublincore", "title",
325 documentModel.getName());
326 } catch (Exception x) {
327 if (logger.isWarnEnabled() == true) {
328 logger.warn("Could not set the Dublin Core 'title' field on document CSID:" +
329 documentModel.getName());
332 documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
333 CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT, now);
334 documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
335 CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY, userId);
338 if (action == Action.CREATE || action == Action.UPDATE) {
339 documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
340 CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT, now);
341 documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
342 CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_BY, userId);
347 * If we see the "rtSbj" query param then we need to perform a CMIS query. Currently, we have only one
348 * CMIS query, but we could add more. If we do, this method should look at the incoming request and corresponding
349 * query params to determine if we need to do a CMIS query
351 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#isCMISQuery()
353 public boolean isCMISQuery() {
354 boolean result = false;
356 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
358 // Look the query params to see if we need to make a CMSIS query.
360 String asSubjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT);
361 String asOjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT);
362 String asEither = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_EITHER);
363 if (asSubjectCsid != null || asOjectCsid != null || asEither != null) {
371 * Creates the CMIS query from the service context. Each document handler is
372 * responsible for returning (can override) a valid CMIS query using the information in the
373 * current service context -which includes things like the query parameters,
376 * This method implementation supports three mutually exclusive cases. We will build a query
377 * that can find a document(s) 'A' in a relationship with another document
378 * 'B' where document 'B' has a CSID equal to the query param passed in and:
379 * 1. Document 'B' is the subject of the relationship
380 * 2. Document 'B' is the object of the relationship
381 * 3. Document 'B' is either the object or the subject of the relationship
384 public String getCMISQuery() {
385 String result = null;
387 if (isCMISQuery() == true) {
388 String docType = this.getServiceContext().getDocumentType();
389 String selectFields = IQueryManager.CMIS_TARGET_CSID + ", "
390 + IQueryManager.CMIS_TARGET_TITLE + ", "
391 + RelationClient.CMIS_CSPACE_RELATIONS_TITLE + ", "
392 + RelationClient.CMIS_CSPACE_RELATIONS_OBJECT_ID + ", "
393 + RelationClient.CMIS_CSPACE_RELATIONS_SUBJECT_ID;
394 String targetTable = docType + " " + IQueryManager.CMIS_TARGET_PREFIX;
395 String relTable = RelationClient.SERVICE_DOC_TYPE + " " + IQueryManager.CMIS_RELATIONS_PREFIX;
396 String relObjectCsidCol = RelationClient.CMIS_CSPACE_RELATIONS_OBJECT_ID;
397 String relSubjectCsidCol = RelationClient.CMIS_CSPACE_RELATIONS_SUBJECT_ID;
398 String targetCsidCol = IQueryManager.CMIS_TARGET_CSID;
400 // Build up the query arguments
402 String theOnClause = "";
403 String theWhereClause = "";
404 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
405 String asSubjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT);
406 String asObjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT);
407 String asEitherCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_EITHER);
410 // Create the "ON" and "WHERE" query clauses based on the params passed into the HTTP request.
412 // First come, first serve -the first match determines the "ON" and "WHERE" query clauses.
414 if (asSubjectCsid != null && !asSubjectCsid.isEmpty()) {
415 // Since our query param is the "subject" value, join the tables where the CSID of the document is the other side (the "object") of the relationship.
416 theOnClause = relObjectCsidCol + " = " + targetCsidCol;
417 theWhereClause = relSubjectCsidCol + " = " + "'" + asSubjectCsid + "'";
418 } else if (asObjectCsid != null && !asObjectCsid.isEmpty()) {
419 // Since our query param is the "object" value, join the tables where the CSID of the document is the other side (the "subject") of the relationship.
420 theOnClause = relSubjectCsidCol + " = " + targetCsidCol;
421 theWhereClause = relObjectCsidCol + " = " + "'" + asObjectCsid + "'";
422 } else if (asEitherCsid != null && !asEitherCsid.isEmpty()) {
423 theOnClause = relObjectCsidCol + " = " + targetCsidCol
424 + " OR " + relSubjectCsidCol + " = " + targetCsidCol;
425 theWhereClause = relSubjectCsidCol + " = " + "'" + asEitherCsid + "'"
426 + " OR " + relObjectCsidCol + " = " + "'" + asEitherCsid + "'";
428 //Since the call to isCMISQuery() return true, we should never get here.
429 logger.error("Attempt to make CMIS query failed because the HTTP request was missing valid query parameters.");
432 // assemble the query from the string arguments
433 result = "SELECT " + selectFields
434 + " FROM " + targetTable + " JOIN " + relTable
435 + " ON " + theOnClause
436 + " WHERE " + theWhereClause;
439 // SELECT D.cmis:name, D.dc:title, R.dc:title, R.relations_common:subjectCsid
440 // FROM Dimension D JOIN Relation R
441 // ON R.relations_common:objectCsid = D.cmis:name
442 // WHERE R.relations_common:subjectCsid = '737527ec-a560-4776-99de'
444 if (logger.isDebugEnabled() == true && result != null) {
445 logger.debug("The CMIS query for the Movement service is: " + result);