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