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