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