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