]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
98ab558c6eac865480880797208b1e0c479f9b90
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
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.
23  */
24 package org.collectionspace.services.nuxeo.client.java;
25
26 import java.util.Collection;
27 import java.util.List;
28
29 import javax.ws.rs.core.MultivaluedMap;
30
31 import org.collectionspace.services.client.Profiler;
32 import org.collectionspace.services.client.CollectionSpaceClient;
33 import org.collectionspace.services.client.IQueryManager;
34 import org.collectionspace.services.client.IRelationsManager;
35 import org.collectionspace.services.client.PoxPayloadIn;
36 import org.collectionspace.services.client.PoxPayloadOut;
37 import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
38 import org.collectionspace.services.common.api.RefName;
39 import org.collectionspace.services.common.api.RefName.RefNameInterface;
40 import org.collectionspace.services.common.authorityref.AuthorityRefList;
41 import org.collectionspace.services.common.context.ServiceContext;
42 import org.collectionspace.services.common.document.AbstractMultipartDocumentHandlerImpl;
43 import org.collectionspace.services.common.document.DocumentFilter;
44 import org.collectionspace.services.common.document.DocumentWrapper;
45 import org.collectionspace.services.common.document.DocumentWrapperImpl;
46 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
47 import org.collectionspace.services.common.query.QueryContext;
48 import org.collectionspace.services.common.repository.RepositoryClient;
49 import org.collectionspace.services.common.repository.RepositoryClientFactory;
50 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
51 import org.collectionspace.services.lifecycle.Lifecycle;
52 import org.collectionspace.services.lifecycle.State;
53 import org.collectionspace.services.lifecycle.StateList;
54 import org.collectionspace.services.lifecycle.TransitionDef;
55 import org.collectionspace.services.lifecycle.TransitionDefList;
56 import org.collectionspace.services.lifecycle.TransitionList;
57
58 import org.nuxeo.ecm.core.NXCore;
59 import org.nuxeo.ecm.core.api.ClientException;
60 import org.nuxeo.ecm.core.api.DocumentModel;
61 import org.nuxeo.ecm.core.api.DocumentModelList;
62 import org.nuxeo.ecm.core.api.model.PropertyException;
63 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
64 import org.nuxeo.ecm.core.lifecycle.LifeCycleService;
65
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 /**
70  * DocumentModelHandler is a base abstract Nuxeo document handler
71  * using Nuxeo Java Remote APIs for CollectionSpace services
72  *
73  * $LastChangedRevision: $
74  * $LastChangedDate: $
75  */
76 public abstract class DocumentModelHandler<T, TL>
77         extends AbstractMultipartDocumentHandlerImpl<T, TL, DocumentModel, DocumentModelList> {
78
79     private final Logger logger = LoggerFactory.getLogger(DocumentModelHandler.class);
80     private RepositoryInstance repositorySession;
81
82     /*
83      * Map Nuxeo's life cycle object to our JAX-B based life cycle object
84      */
85     private Lifecycle createCollectionSpaceLifecycle(org.nuxeo.ecm.core.lifecycle.LifeCycle nuxeoLifecyle) {
86         Lifecycle result = null;
87         
88         if (nuxeoLifecyle != null) {
89                 //
90                 // Copy the life cycle's name
91                 result = new Lifecycle();
92                 result.setName(nuxeoLifecyle.getName());
93                 
94                 // We currently support only one initial state, so take the first one from Nuxeo
95                 Collection<String> initialStateNames = nuxeoLifecyle.getInitialStateNames();
96                 result.setDefaultInitial(initialStateNames.iterator().next());
97                 
98                 // Next, we copy the state and corresponding transition lists
99                 StateList stateList = new StateList();
100                 List<State> states = stateList.getState();
101                 Collection<org.nuxeo.ecm.core.lifecycle.LifeCycleState> nuxeoStates = nuxeoLifecyle.getStates();
102                 for (org.nuxeo.ecm.core.lifecycle.LifeCycleState nuxeoState : nuxeoStates) {
103                         State tempState = new State();
104                         tempState.setDescription(nuxeoState.getDescription());
105                         tempState.setInitial(nuxeoState.isInitial());
106                         tempState.setName(nuxeoState.getName());
107                         // Now get the list of transitions
108                         TransitionList transitionList = new TransitionList();
109                         List<String> transitions = transitionList.getTransition();
110                         Collection<String> nuxeoTransitions = nuxeoState.getAllowedStateTransitions();
111                         for (String nuxeoTransition : nuxeoTransitions) {
112                                 transitions.add(nuxeoTransition);
113                         }
114                         tempState.setTransitionList(transitionList);
115                         states.add(tempState);
116                 }
117                 result.setStateList(stateList);
118                 
119                 // Finally, we create the transition definitions
120                 TransitionDefList transitionDefList = new TransitionDefList();
121                 List<TransitionDef> transitionDefs = transitionDefList.getTransitionDef();
122                 Collection<org.nuxeo.ecm.core.lifecycle.LifeCycleTransition> nuxeoTransitionDefs = nuxeoLifecyle.getTransitions();
123                 for (org.nuxeo.ecm.core.lifecycle.LifeCycleTransition nuxeoTransitionDef : nuxeoTransitionDefs) {
124                         TransitionDef tempTransitionDef = new TransitionDef();
125                         tempTransitionDef.setDescription(nuxeoTransitionDef.getDescription());
126                         tempTransitionDef.setDestinationState(nuxeoTransitionDef.getDestinationStateName());
127                         tempTransitionDef.setName(nuxeoTransitionDef.getName());
128                         transitionDefs.add(tempTransitionDef);
129                 }
130                 result.setTransitionDefList(transitionDefList);
131         }
132         
133         return result;
134     }
135     
136     /*
137      * Returns the the life cycle definition of the related Nuxeo document type for this handler.
138      * (non-Javadoc)
139      * @see org.collectionspace.services.common.document.DocumentHandler#getLifecycle()
140      */
141     @Override
142     public Lifecycle getLifecycle() {
143         Lifecycle result = null;
144         
145         String docTypeName = null;
146         try {
147                 docTypeName = this.getServiceContext().getDocumentType();
148                 result = getLifecycle(docTypeName);
149         } catch (Exception e) {
150                 if (logger.isTraceEnabled() == true) {
151                         logger.trace("Could not retrieve lifecycle definition for Nuxeo doctype: " + docTypeName);
152                 }
153         }
154         
155         return result;
156     }
157     
158     /*
159      * Returns the the life cycle definition of the related Nuxeo document type for this handler.
160      * (non-Javadoc)
161      * @see org.collectionspace.services.common.document.DocumentHandler#getLifecycle(java.lang.String)
162      */
163     @Override
164     public Lifecycle getLifecycle(String docTypeName) {
165         org.nuxeo.ecm.core.lifecycle.LifeCycle nuxeoLifecyle;
166         Lifecycle result = null;
167         
168         try {
169                 LifeCycleService lifeCycleService = null;
170                         try {
171                                 lifeCycleService = NXCore.getLifeCycleService();
172                         } catch (Exception e) {
173                                 e.printStackTrace();
174                         }
175                         
176                 String lifeCycleName; 
177                 lifeCycleName = lifeCycleService.getLifeCycleNameFor(docTypeName);
178                 nuxeoLifecyle = lifeCycleService.getLifeCycleByName(lifeCycleName);
179                 
180                 result = createCollectionSpaceLifecycle(nuxeoLifecyle); 
181 //                      result = (Lifecycle)FileTools.getJaxbObjectFromFile(Lifecycle.class, "default-lifecycle.xml");
182                 } catch (Exception e) {
183                         // TODO Auto-generated catch block
184                         logger.error("Could not retreive life cycle information for Nuxeo doctype: " + docTypeName, e);
185                 }
186         
187         return result;
188     }
189     
190     /*
191      * We're using the "name" field of Nuxeo's DocumentModel to store
192      * the CSID.
193      */
194     public String getCsid(DocumentModel docModel) {
195         return NuxeoUtils.getCsid(docModel);
196     }
197
198     public String getUri(DocumentModel docModel) {
199         return getServiceContextPath()+getCsid(docModel);
200     }
201         
202     public RepositoryClient<PoxPayloadIn, PoxPayloadOut> getRepositoryClient(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
203         RepositoryClient<PoxPayloadIn, PoxPayloadOut> repositoryClient = RepositoryClientFactory.getInstance().getClient(ctx.getRepositoryClientName());
204         return repositoryClient;
205     }
206
207     /**
208      * getRepositorySession returns Nuxeo Repository Session
209      * @return
210      */
211     public RepositoryInstance getRepositorySession() {
212         
213         return repositorySession;
214     }
215
216     /**
217      * setRepositorySession sets repository session
218      * @param repoSession
219      */
220     public void setRepositorySession(RepositoryInstance repoSession) {
221         this.repositorySession = repoSession;
222     }
223
224     @Override
225     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
226         // TODO for sub-docs - check to see if the current service context is a multipart input, 
227         // OR a docfragment, and call a variant to fill the DocModel.
228         fillAllParts(wrapDoc, Action.CREATE);
229         handleCoreValues(wrapDoc, Action.CREATE);
230     }
231     
232     // TODO for sub-docs - Add completeCreate in which we look for set-aside doc fragments 
233     // and create the subitems. We will create service contexts with the doc fragments
234     // and then call 
235
236
237     @Override
238     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
239         // TODO for sub-docs - check to see if the current service context is a multipart input, 
240         // OR a docfragment, and call a variant to fill the DocModel.
241         fillAllParts(wrapDoc, Action.UPDATE);
242         handleCoreValues(wrapDoc, Action.UPDATE);
243     }
244
245     @Override
246     public void handleGet(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
247         extractAllParts(wrapDoc);
248     }
249
250     @Override
251     public void handleGetAll(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
252         Profiler profiler = new Profiler(this, 2);
253         profiler.start();
254         setCommonPartList(extractCommonPartList(wrapDoc));
255         profiler.stop();
256     }
257
258     @Override
259     public abstract void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
260
261     @Override
262     public abstract void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
263
264     @Override
265     public abstract T extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
266
267     @Override
268     public abstract void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception;
269
270     @Override
271     public abstract void fillCommonPart(T obj, DocumentWrapper<DocumentModel> wrapDoc) throws Exception;
272
273     @Override
274     public abstract TL extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception;
275
276     @Override
277     public abstract T getCommonPart();
278
279     @Override
280     public abstract void setCommonPart(T obj);
281
282     @Override
283     public abstract TL getCommonPartList();
284
285     @Override
286     public abstract void setCommonPartList(TL obj);
287     
288     @Override
289     public DocumentFilter createDocumentFilter() {
290         DocumentFilter filter = new NuxeoDocumentFilter(this.getServiceContext());
291         return filter;
292     }
293     
294     /**
295      * Gets the authority refs.
296      *
297      * @param docWrapper the doc wrapper
298      * @param authRefFields the auth ref fields
299      * @return the authority refs
300      * @throws PropertyException the property exception
301      */
302     abstract public AuthorityRefList getAuthorityRefs(String csid,
303                 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException;    
304
305     /*
306      * Subclasses should override this method if they need to customize their refname generation
307      */
308     protected RefName.RefNameInterface getRefName(ServiceContext ctx,
309                 DocumentModel docModel) {
310         return getRefName(new DocumentWrapperImpl<DocumentModel>(docModel), ctx.getTenantName(), ctx.getServiceName());
311     }
312     
313     /*
314      * By default, we'll use the CSID as the short ID.  Sub-classes can override this method if they want to use
315      * something else for a short ID.
316      * 
317      * (non-Javadoc)
318      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#getRefName(org.collectionspace.services.common.document.DocumentWrapper, java.lang.String, java.lang.String)
319      */
320     @Override
321         protected RefName.RefNameInterface getRefName(DocumentWrapper<DocumentModel> docWrapper,
322                         String tenantName, String serviceName) {
323         String csid = docWrapper.getWrappedObject().getName();
324         String refnameDisplayName = getRefnameDisplayName(docWrapper);
325         RefName.RefNameInterface refname = RefName.Authority.buildAuthority(tenantName, serviceName,
326                         csid, null, refnameDisplayName);
327         return refname;
328         }
329
330     private void handleCoreValues(DocumentWrapper<DocumentModel> docWrapper, 
331                 Action action)  throws ClientException {
332         DocumentModel documentModel = docWrapper.getWrappedObject();
333         String now = GregorianCalendarDateTimeUtils.timestampUTC();
334         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
335         String userId = ctx.getUserId();
336         if (action == Action.CREATE) {
337             //
338             // Add the tenant ID value to the new entity
339             //
340                 String tenantId = ctx.getTenantId();
341             documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
342                         CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID, tenantId);
343             //
344             // Add the uri value to the new entity
345             //
346             documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
347                         CollectionSpaceClient.COLLECTIONSPACE_CORE_URI, getUri(documentModel));
348                 //
349                 // Add the CSID to the DublinCore title so we can see the CSID in the default
350                 // Nuxeo webapp.
351                 //
352                 try {
353                 documentModel.setProperty("dublincore", "title",
354                         documentModel.getName());
355                 } catch (Exception x) {
356                         if (logger.isWarnEnabled() == true) {
357                                 logger.warn("Could not set the Dublin Core 'title' field on document CSID:" +
358                                                 documentModel.getName());
359                         }
360                 }
361                 //
362                 // Add createdAt timestamp and createdBy user
363                 //
364             documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
365                         CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT, now);
366             documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
367                         CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY, userId);
368         }
369         
370                 if (action == Action.CREATE || action == Action.UPDATE) {
371             //
372             // Add the resource's refname
373             //
374             RefNameInterface refname = getRefName(ctx, documentModel); // Sub-classes may override the getRefName() method called here.
375             if (refname != null) {
376                 String refnameStr = refname.toString();
377                     documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
378                                 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME, refnameStr);
379             }
380             //
381             // Add updatedAt timestamp and updateBy user
382             //
383                         documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
384                                         CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT, now);
385                         documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
386                                         CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_BY, userId);
387                 }               
388     }
389         
390     /*
391      * If we see the "rtSbj" query param then we need to perform a CMIS query.  Currently, we have only one
392      * CMIS query, but we could add more.  If we do, this method should look at the incoming request and corresponding
393      * query params to determine if we need to do a CMIS query
394      * (non-Javadoc)
395      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#isCMISQuery()
396      */
397     public boolean isCMISQuery() {
398         boolean result = false;
399         
400         MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
401         //
402         // Look the query params to see if we need to make a CMSIS query.
403         //
404         String asSubjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT);           
405         String asOjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT);      
406         String asEither = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_EITHER);         
407         if (asSubjectCsid != null || asOjectCsid != null || asEither != null) {
408                 result = true;
409         }
410         
411         return result;
412     }
413     
414         /**
415          * Creates the CMIS query from the service context. Each document handler is
416          * responsible for returning (can override) a valid CMIS query using the information in the
417          * current service context -which includes things like the query parameters,
418          * etc.
419          * 
420          * This method implementation supports three mutually exclusive cases. We will build a query
421          * that can find a document(s) 'A' in a relationship with another document
422          * 'B' where document 'B' has a CSID equal to the query param passed in and:
423          *              1. Document 'B' is the subject of the relationship
424          *              2. Document 'B' is the object of the relationship
425          *              3. Document 'B' is either the object or the subject of the relationship
426          */
427     @Override
428     public String getCMISQuery(QueryContext queryContext) {
429         String result = null;
430         
431         if (isCMISQuery() == true) {
432                 //
433                 // Build up the query arguments
434                 //
435                 String theOnClause = "";
436                 String theWhereClause = "";
437                 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
438                 String asSubjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT);
439                 String asObjectCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT);
440                 String asEitherCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_EITHER);
441                 String matchObjDocTypes = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_MATCH_OBJ_DOCTYPES);
442                 String selectDocType = (String)queryParams.getFirst(IQueryManager.SELECT_DOC_TYPE_FIELD);
443
444                 String docType = this.getServiceContext().getDocumentType();
445                 if (selectDocType != null && !selectDocType.isEmpty()) {  
446                         docType = selectDocType;
447                 }
448                 String selectFields = IQueryManager.CMIS_TARGET_CSID + ", "
449                                 + IQueryManager.CMIS_TARGET_TITLE + ", "
450                                 + IRelationsManager.CMIS_CSPACE_RELATIONS_TITLE + ", "
451                                 + IRelationsManager.CMIS_CSPACE_RELATIONS_OBJECT_ID + ", "
452                                 + IRelationsManager.CMIS_CSPACE_RELATIONS_SUBJECT_ID;
453                 String targetTable = docType + " " + IQueryManager.CMIS_TARGET_PREFIX;
454                 String relTable = IRelationsManager.DOC_TYPE + " " + IQueryManager.CMIS_RELATIONS_PREFIX;
455                 String relObjectCsidCol = IRelationsManager.CMIS_CSPACE_RELATIONS_OBJECT_ID;
456                 String relSubjectCsidCol = IRelationsManager.CMIS_CSPACE_RELATIONS_SUBJECT_ID;
457                 String targetCsidCol = IQueryManager.CMIS_TARGET_CSID;
458
459                 //
460                 // Create the "ON" and "WHERE" query clauses based on the params passed into the HTTP request.  
461                 //
462                 // First come, first serve -the first match determines the "ON" and "WHERE" query clauses.
463                 //
464                 if (asSubjectCsid != null && !asSubjectCsid.isEmpty()) {  
465                         // 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.
466                         theOnClause = relObjectCsidCol + " = " + targetCsidCol;
467                         theWhereClause = relSubjectCsidCol + " = " + "'" + asSubjectCsid + "'";
468                 } else if (asObjectCsid != null && !asObjectCsid.isEmpty()) {
469                         // 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.
470                         theOnClause = relSubjectCsidCol + " = " + targetCsidCol; 
471                         theWhereClause = relObjectCsidCol + " = " + "'" + asObjectCsid + "'";
472                 } else if (asEitherCsid != null && !asEitherCsid.isEmpty()) {
473                         theOnClause = relObjectCsidCol + " = " + targetCsidCol
474                                         + " OR " + relSubjectCsidCol + " = " + targetCsidCol;
475                         theWhereClause = relSubjectCsidCol + " = " + "'" + asEitherCsid + "'"
476                                         + " OR " + relObjectCsidCol + " = " + "'" + asEitherCsid + "'";
477                 } else {
478                         //Since the call to isCMISQuery() return true, we should never get here.
479                         logger.error("Attempt to make CMIS query failed because the HTTP request was missing valid query parameters.");
480                 }
481                 
482                 // Now consider a constraint on the object doc types (for search by service group)
483                 if (matchObjDocTypes != null && !matchObjDocTypes.isEmpty()) {  
484                         // 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.
485                         theWhereClause += " AND (" + IRelationsManager.CMIS_CSPACE_RELATIONS_OBJECT_TYPE 
486                                                                 + " IN " + matchObjDocTypes + ")";
487                 }
488                 
489                 StringBuilder query = new StringBuilder();
490                 // assemble the query from the string arguments
491                 query.append("SELECT ");
492                 query.append(selectFields);
493                 query.append(" FROM " + targetTable + " JOIN " + relTable);
494                 query.append(" ON " + theOnClause);
495                 query.append(" WHERE " + theWhereClause);
496                 
497                 try {
498                                 NuxeoUtils.appendCMISOrderBy(query, queryContext);
499                         } catch (Exception e) {
500                                 logger.error("Could not append ORDER BY clause to CMIS query", e);
501                         }
502                 
503                 // An example:
504                 // SELECT D.cmis:name, D.dc:title, R.dc:title, R.relations_common:subjectCsid
505                 // FROM Dimension D JOIN Relation R
506                 // ON R.relations_common:objectCsid = D.cmis:name
507                 // WHERE R.relations_common:subjectCsid = '737527ec-a560-4776-99de'
508                 // ORDER BY D.collectionspace_core:updatedAt DESC
509                 
510                 result = query.toString();
511                 if (logger.isDebugEnabled() == true && result != null) {
512                         logger.debug("The CMIS query is: " + result);
513                 }
514         }
515         
516         return result;
517     }
518     
519 }