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