]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
daf3348ab9afc5fd87d0e7f9d763641fa80b8e41
[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 package org.collectionspace.services.nuxeo.client.java;
19
20 import java.util.Hashtable;
21 import java.util.List;
22 import java.util.UUID;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 import java.util.regex.PatternSyntaxException;
26
27 import org.collectionspace.services.client.IQueryManager;
28 import org.collectionspace.services.client.PoxPayloadIn;
29 import org.collectionspace.services.client.PoxPayloadOut;
30 import org.collectionspace.services.common.context.ServiceContext;
31 import org.collectionspace.services.common.datetime.GregorianCalendarDateTimeUtils;
32 import org.collectionspace.services.common.query.QueryContext;
33 import org.collectionspace.services.common.repository.RepositoryClient;
34 import org.collectionspace.services.common.profile.Profiler;
35 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
36
37 import org.collectionspace.services.common.document.BadRequestException;
38 import org.collectionspace.services.common.document.DocumentException;
39 import org.collectionspace.services.common.document.DocumentFilter;
40 import org.collectionspace.services.common.document.DocumentHandler;
41 import org.collectionspace.services.common.document.DocumentNotFoundException;
42 import org.collectionspace.services.common.document.DocumentHandler.Action;
43 import org.collectionspace.services.common.document.DocumentWrapper;
44 import org.collectionspace.services.common.document.DocumentWrapperImpl;
45
46 import org.nuxeo.common.utils.IdUtils;
47 import org.nuxeo.ecm.core.api.ClientException;
48 import org.nuxeo.ecm.core.api.DocumentModel;
49 import org.nuxeo.ecm.core.api.DocumentModelList;
50 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
51 import org.nuxeo.ecm.core.api.DocumentRef;
52 import org.nuxeo.ecm.core.api.IdRef;
53 import org.nuxeo.ecm.core.api.PathRef;
54 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
55 import org.nuxeo.ecm.core.client.NuxeoClient;
56
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 /**
61  * RepositoryJavaClient is used to perform CRUD operations on documents in Nuxeo
62  * repository using Remote Java APIs. It uses @see DocumentHandler as IOHandler
63  * with the client.
64  * 
65  * $LastChangedRevision: $ $LastChangedDate: $
66  */
67 public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn, PoxPayloadOut> {
68
69     /** The logger. */
70     private final Logger logger = LoggerFactory.getLogger(RepositoryJavaClientImpl.class);
71 //    private final Logger profilerLogger = LoggerFactory.getLogger("remperf");
72 //    private String foo = Profiler.createLogger();
73
74     // Regular expressions pattern for identifying valid ORDER BY clauses.
75     // FIXME: Currently supports only USASCII word characters in field names.
76     final String ORDER_BY_CLAUSE_REGEX = "\\w+(_\\w+)?:\\w+( ASC| DESC)?(, \\w+(_\\w+)?:\\w+( ASC| DESC)?)*";
77
78     /**
79      * Instantiates a new repository java client impl.
80      */
81     public RepositoryJavaClientImpl() {
82         //Empty constructor
83         
84     }
85
86     /**
87      * Sets the collection space core values.
88      *
89      * @param ctx the ctx
90      * @param documentModel the document model
91      * @throws ClientException the client exception
92      */
93     private void setCollectionSpaceCoreValues(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
94             DocumentModel documentModel,
95             Action action) throws ClientException {
96         //
97         // Add the CSID to the DublinCore title so we can see the CSID in the default
98         // Nuxeo webapp.
99         //
100         try {
101                 documentModel.setProperty("dublincore",
102                         "title",
103                         documentModel.getName());
104         } catch (Exception x) {
105                 if (logger.isWarnEnabled() == true) {
106                         logger.warn("Could not set the Dublin Core 'title' field on document CSID:" +
107                                         documentModel.getName());
108                 }
109         }
110         //
111         // Add the tenant ID value to the new entity
112         //
113         documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
114                 DocumentModelHandler.COLLECTIONSPACE_CORE_TENANTID,
115                 ctx.getTenantId());
116
117         String now = GregorianCalendarDateTimeUtils.timestampUTC();
118         
119         switch (action) {
120             case CREATE:
121                 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
122                                 DocumentModelHandler.COLLECTIONSPACE_CORE_CREATED_AT,
123                                 now);
124                 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
125                                                 DocumentModelHandler.COLLECTIONSPACE_CORE_UPDATED_AT,
126                                                 now);
127                 break;
128             case UPDATE:
129                 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
130                                 DocumentModelHandler.COLLECTIONSPACE_CORE_UPDATED_AT,
131                                 now);
132
133                 break;
134             default:
135         }
136     }
137
138     /**
139      * create document in the Nuxeo repository
140      *
141      * @param ctx service context under which this method is invoked
142      * @param handler
143      *            should be used by the caller to provide and transform the
144      *            document
145      * @return id in repository of the newly created document
146      * @throws DocumentException
147      */
148     @Override
149     public String create(ServiceContext ctx,
150             DocumentHandler handler) throws BadRequestException,
151             DocumentException {
152
153         if (ctx.getDocumentType() == null) {
154             throw new IllegalArgumentException(
155                     "RepositoryJavaClient.create: docType is missing");
156         }
157         if (handler == null) {
158             throw new IllegalArgumentException(
159                     "RepositoryJavaClient.create: handler is missing");
160         }
161         String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
162         if (nuxeoWspaceId == null) {
163             throw new DocumentNotFoundException(
164                     "Unable to find workspace for service " + ctx.getServiceName()
165                     + " check if the workspace exists in the Nuxeo repository");
166         }
167         RepositoryInstance repoSession = null;
168         try {
169             handler.prepare(Action.CREATE);
170             repoSession = getRepositorySession();
171             DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId);
172             DocumentModel wspaceDoc = repoSession.getDocument(nuxeoWspace);
173             String wspacePath = wspaceDoc.getPathAsString();
174             //give our own ID so PathRef could be constructed later on
175             String id = IdUtils.generateId(UUID.randomUUID().toString());
176             // create document model
177             DocumentModel doc = repoSession.createDocumentModel(wspacePath, id,
178                     ctx.getDocumentType());
179             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
180             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
181             handler.handle(Action.CREATE, wrapDoc);
182             // create document with documentmodel
183             setCollectionSpaceCoreValues(ctx, doc, Action.CREATE);
184             doc = repoSession.createDocument(doc);
185             repoSession.save();
186 // TODO for sub-docs need to call into the handler to let it deal with subitems. Pass in the id,
187 // and assume the handler has the state it needs (doc fragments). 
188             handler.complete(Action.CREATE, wrapDoc);
189             return id;
190         } catch (BadRequestException bre) {
191             throw bre;
192         } catch (Exception e) {
193             if (logger.isDebugEnabled()) {
194                 logger.debug("Caught exception ", e);
195             }
196             throw new DocumentException(e);
197         } finally {
198             if (repoSession != null) {
199                 releaseRepositorySession(repoSession);
200             }
201         }
202
203     }
204
205     /**
206      * get document from the Nuxeo repository
207      * @param ctx service context under which this method is invoked
208      * @param id
209      *            of the document to retrieve
210      * @param handler
211      *            should be used by the caller to provide and transform the
212      *            document
213      * @throws DocumentException
214      */
215     @Override
216     public void get(ServiceContext ctx, String id, DocumentHandler handler)
217             throws DocumentNotFoundException, DocumentException {
218
219         if (handler == null) {
220             throw new IllegalArgumentException(
221                     "RepositoryJavaClient.get: handler is missing");
222         }
223         RepositoryInstance repoSession = null;
224
225         try {
226             handler.prepare(Action.GET);
227             repoSession = getRepositorySession();
228             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
229             DocumentModel doc = null;
230             try {
231                 doc = repoSession.getDocument(docRef);
232             } catch (ClientException ce) {
233                 String msg = "could not find document with id=" + id;
234                 logger.error(msg, ce);
235                 throw new DocumentNotFoundException(msg, ce);
236             }
237             //set reposession to handle the document
238             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
239             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
240             handler.handle(Action.GET, wrapDoc);
241             handler.complete(Action.GET, wrapDoc);
242         } catch (IllegalArgumentException iae) {
243             throw iae;
244         } catch (DocumentException de) {
245             throw de;
246         } catch (Exception e) {
247             if (logger.isDebugEnabled()) {
248                 logger.debug("Caught exception ", e);
249             }
250             throw new DocumentException(e);
251         } finally {
252             if (repoSession != null) {
253                 releaseRepositorySession(repoSession);
254             }
255         }
256     }
257
258     /**
259      * get document from the Nuxeo repository, using the docFilter params.
260      * @param ctx service context under which this method is invoked
261      * @param handler
262      *            should be used by the caller to provide and transform the
263      *            document. Handler must have a docFilter set to return a single item.
264      * @throws DocumentException
265      */
266     @Override
267     public void get(ServiceContext ctx, DocumentHandler handler)
268             throws DocumentNotFoundException, DocumentException {
269         QueryContext queryContext = new QueryContext(ctx, handler);
270         RepositoryInstance repoSession = null;
271
272         try {
273             handler.prepare(Action.GET);
274             repoSession = getRepositorySession();
275
276             DocumentModelList docList = null;
277             // force limit to 1, and ignore totalSize
278             String query = buildNXQLQuery(queryContext);
279             docList = repoSession.query(query, null, 1, 0, false);
280             if (docList.size() != 1) {
281                 throw new DocumentNotFoundException("No document found matching filter params.");
282             }
283             DocumentModel doc = docList.get(0);
284
285             if (logger.isDebugEnabled()) {
286                 logger.debug("Executed NXQL query: " + query);
287             }
288
289             //set reposession to handle the document
290             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
291             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
292             handler.handle(Action.GET, wrapDoc);
293             handler.complete(Action.GET, wrapDoc);
294         } catch (IllegalArgumentException iae) {
295             throw iae;
296         } catch (DocumentException de) {
297             throw de;
298         } catch (Exception e) {
299             if (logger.isDebugEnabled()) {
300                 logger.debug("Caught exception ", e);
301             }
302             throw new DocumentException(e);
303         } finally {
304             if (repoSession != null) {
305                 releaseRepositorySession(repoSession);
306             }
307         }
308     }
309     
310     private static String getByNameWhereClause(String csid) {
311         String result = null;
312         
313         if (csid != null) {
314                 result = "ecm:name = " + "\'" + csid + "\'";
315         }
316         
317         return result;
318     }
319     
320     /**
321      * Get wrapped documentModel from the Nuxeo repository.  The search is restricted to the workspace
322      * of the current context.
323      * 
324      * @param ctx service context under which this method is invoked
325      * @param id
326      *            of the document to retrieve
327      * @throws DocumentException
328      */
329     @Override
330     public DocumentWrapper<DocumentModel> getDoc(
331             ServiceContext ctx, String csid)
332             throws DocumentNotFoundException, DocumentException {
333         RepositoryInstance repoSession = null;
334         DocumentWrapper<DocumentModel> wrapDoc = null;
335
336         try {
337             repoSession = getRepositorySession();
338             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
339             DocumentModel doc = null;
340             try {
341                 doc = repoSession.getDocument(docRef);
342             } catch (ClientException ce) {
343                 String msg = "could not find document with id=" + csid;
344                 logger.error(msg, ce);
345                 throw new DocumentNotFoundException(msg, ce);
346             }
347             wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
348         } catch (IllegalArgumentException iae) {
349             throw iae;
350         } catch (DocumentException de) {
351             throw de;
352         } catch (Exception e) {
353             if (logger.isDebugEnabled()) {
354                 logger.debug("Caught exception ", e);
355             }
356             throw new DocumentException(e);
357         } finally {
358             if (repoSession != null) {
359                 releaseRepositorySession(repoSession);
360             }
361         }
362         return wrapDoc;
363     }
364
365     /**
366      * find wrapped documentModel from the Nuxeo repository
367      * @param ctx service context under which this method is invoked
368      * @param whereClause where NXQL where clause to get the document
369      * @throws DocumentException
370      */
371     @Override
372     public DocumentWrapper<DocumentModel> findDoc(
373             ServiceContext ctx, String whereClause)
374             throws DocumentNotFoundException, DocumentException {
375         RepositoryInstance repoSession = null;
376         DocumentWrapper<DocumentModel> wrapDoc = null;
377
378         try {
379             QueryContext queryContext = new QueryContext(ctx, whereClause);
380             repoSession = getRepositorySession();
381             DocumentModelList docList = null;
382             // force limit to 1, and ignore totalSize
383             String query = buildNXQLQuery(queryContext);
384             docList = repoSession.query(query,
385                     null, //Filter
386                     1, //limit
387                     0, //offset
388                     false); //countTotal
389             if (docList.size() != 1) {
390                 if (logger.isDebugEnabled()) {
391                     logger.debug("findDoc: Query found: " + docList.size() + " items.");
392                     logger.debug(" Query: " + query);
393                 }
394                 throw new DocumentNotFoundException("No document found matching filter params.");
395             }
396             DocumentModel doc = docList.get(0);
397             wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
398         } catch (IllegalArgumentException iae) {
399             throw iae;
400         } catch (DocumentException de) {
401             throw de;
402         } catch (Exception e) {
403             if (logger.isDebugEnabled()) {
404                 logger.debug("Caught exception ", e);
405             }
406             throw new DocumentException(e);
407         } finally {
408             if (repoSession != null) {
409                 releaseRepositorySession(repoSession);
410             }
411         }
412         return wrapDoc;
413     }
414
415     /**
416      * find doc and return CSID from the Nuxeo repository
417      * @param ctx service context under which this method is invoked
418      * @param whereClause where NXQL where clause to get the document
419      * @throws DocumentException
420      */
421     @Override
422     public String findDocCSID(
423             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String whereClause)
424             throws DocumentNotFoundException, DocumentException {
425         String csid = null;
426         try {
427             DocumentWrapper<DocumentModel> wrapDoc = findDoc(ctx, whereClause);
428             DocumentModel docModel = wrapDoc.getWrappedObject();
429             csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
430         } catch (DocumentNotFoundException dnfe) {
431             throw dnfe;
432         } catch (IllegalArgumentException iae) {
433             throw iae;
434         } catch (DocumentException de) {
435             throw de;
436         } catch (Exception e) {
437             if (logger.isDebugEnabled()) {
438                 logger.debug("Caught exception ", e);
439             }
440             throw new DocumentException(e);
441         }
442         return csid;
443     }
444
445     /**
446      * Find a list of documentModels from the Nuxeo repository
447      * @param docTypes a list of DocType names to match
448      * @param  whereClause where the clause to qualify on
449      * @return
450      */
451     @Override
452     public DocumentWrapper<DocumentModelList> findDocs(
453             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
454             List<String> docTypes,
455             String whereClause,
456             int pageSize, int pageNum, boolean computeTotal)
457             throws DocumentNotFoundException, DocumentException {
458         RepositoryInstance repoSession = null;
459         DocumentWrapper<DocumentModelList> wrapDoc = null;
460
461         try {
462             if (docTypes == null || docTypes.size() < 1) {
463                 throw new DocumentNotFoundException(
464                         "findDocs must specify at least one DocumentType.");
465             }
466             repoSession = getRepositorySession();
467             DocumentModelList docList = null;
468             // force limit to 1, and ignore totalSize
469             QueryContext queryContext = new QueryContext(ctx, whereClause);
470             String query = buildNXQLQuery(docTypes, queryContext);
471             docList = repoSession.query(query, null, pageSize, pageNum, computeTotal);
472             wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
473         } catch (IllegalArgumentException iae) {
474             throw iae;
475         } catch (Exception e) {
476             if (logger.isDebugEnabled()) {
477                 logger.debug("Caught exception ", e);
478             }
479             throw new DocumentException(e);
480         } finally {
481             if (repoSession != null) {
482                 releaseRepositorySession(repoSession);
483             }
484         }
485         return wrapDoc;
486     }
487
488     /* (non-Javadoc)
489      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
490      */
491     @Override
492     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
493             throws DocumentNotFoundException, DocumentException {
494         if (handler == null) {
495             throw new IllegalArgumentException(
496                     "RepositoryJavaClient.getAll: handler is missing");
497         }
498
499         RepositoryInstance repoSession = null;
500
501         try {
502             handler.prepare(Action.GET_ALL);
503             repoSession = getRepositorySession();
504             DocumentModelList docModelList = new DocumentModelListImpl();
505             //FIXME: Should be using NuxeoUtils.createPathRef for security reasons
506             for (String csid : csidList) {
507                 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
508                 DocumentModel docModel = repoSession.getDocument(docRef);
509                 docModelList.add(docModel);
510             }
511
512             //set reposession to handle the document
513             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
514             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docModelList);
515             handler.handle(Action.GET_ALL, wrapDoc);
516             handler.complete(Action.GET_ALL, wrapDoc);
517         } catch (DocumentException de) {
518             throw de;
519         } catch (Exception e) {
520             if (logger.isDebugEnabled()) {
521                 logger.debug("Caught exception ", e);
522             }
523             throw new DocumentException(e);
524         } finally {
525             if (repoSession != null) {
526                 releaseRepositorySession(repoSession);
527             }
528         }
529     }
530
531     /**
532      * getAll get all documents for an entity entity service from the Nuxeo
533      * repository
534      *
535      * @param ctx service context under which this method is invoked
536      * @param handler
537      *            should be used by the caller to provide and transform the
538      *            document
539      * @throws DocumentException
540      */
541     @Override
542     public void getAll(ServiceContext ctx, DocumentHandler handler)
543             throws DocumentNotFoundException, DocumentException {
544         if (handler == null) {
545             throw new IllegalArgumentException(
546                     "RepositoryJavaClient.getAll: handler is missing");
547         }
548         String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
549         if (nuxeoWspaceId == null) {
550             throw new DocumentNotFoundException(
551                     "Unable to find workspace for service "
552                     + ctx.getServiceName()
553                     + " check if the workspace exists in the Nuxeo repository");
554         }
555         RepositoryInstance repoSession = null;
556
557         try {
558             handler.prepare(Action.GET_ALL);
559             repoSession = getRepositorySession();
560             DocumentRef wsDocRef = new IdRef(nuxeoWspaceId);
561             DocumentModelList docList = repoSession.getChildren(wsDocRef);
562             //set reposession to handle the document
563             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
564             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
565             handler.handle(Action.GET_ALL, wrapDoc);
566             handler.complete(Action.GET_ALL, wrapDoc);
567         } catch (DocumentException de) {
568             throw de;
569         } catch (Exception e) {
570             if (logger.isDebugEnabled()) {
571                 logger.debug("Caught exception ", e);
572             }
573             throw new DocumentException(e);
574         } finally {
575             if (repoSession != null) {
576                 releaseRepositorySession(repoSession);
577             }
578         }
579     }
580     
581     private boolean isClauseEmpty(String theString) {
582         boolean result = true;
583         if (theString != null && !theString.isEmpty()) {
584                 result = false;
585         }
586         return result;
587     }
588
589     /*
590      * A method to find a CollectionSpace document (of any type) given just a service context and
591      * its CSID.  A search across *all* service workspaces (within a given tenant context) is performed to find
592      * the document
593      * 
594      * This query searches Nuxeo's Hierarchy table where our CSIDs are stored in the "name" column.
595      */
596     @Override
597     public DocumentWrapper<DocumentModel> getDocFromCsid(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
598                 String csid)
599             throws Exception {
600         DocumentWrapper<DocumentModel> result = null;
601         RepositoryInstance repoSession = getRepositorySession();
602         DocumentModelList docModelList = null;
603         //
604         // Set of query context using the current service context, but change the document type
605         // to be the base Nuxeo document type so we can look for the document across service workspaces
606         //
607         QueryContext queryContext = new QueryContext(ctx, getByNameWhereClause(csid));
608         queryContext.setDocType(NuxeoUtils.BASE_DOCUMENT_TYPE);
609         //
610         // Since we're doing a query, we get back a list so we need to make sure there is only
611         // a single result since CSID values are supposed to be unique.
612         String query = buildNXQLQuery(queryContext);
613         docModelList = repoSession.query(query);
614         long resultSize = docModelList.totalSize();
615         if (resultSize == 1) {
616                 result = new DocumentWrapperImpl<DocumentModel>(docModelList.get(0));
617         } else if (resultSize > 1) {
618                 throw new DocumentException("Found more than 1 document with CSID = " + csid);
619         }
620         
621         return result;
622     }
623
624
625     /**
626      * getFiltered get all documents for an entity service from the Document repository,
627      * given filter parameters specified by the handler. 
628      * @param ctx service context under which this method is invoked
629      * @param handler should be used by the caller to provide and transform the document
630      * @throws DocumentNotFoundException if workspace not found
631      * @throws DocumentException
632      */
633     @Override
634     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
635             throws DocumentNotFoundException, DocumentException {
636
637         DocumentFilter filter = handler.getDocumentFilter();
638         String oldOrderBy = filter.getOrderByClause();
639         if (isClauseEmpty(oldOrderBy) == true){
640             filter.setOrderByClause(DocumentFilter.ORDER_BY_LAST_UPDATED);  //per http://issues.collectionspace.org/browse/CSPACE-705
641         }
642         QueryContext queryContext = new QueryContext(ctx, handler);
643         RepositoryInstance repoSession = null;
644         try {
645             handler.prepare(Action.GET_ALL);
646             repoSession = getRepositorySession();
647             DocumentModelList docList = null;
648             String query = buildNXQLQuery(queryContext);
649
650             if (logger.isDebugEnabled()) {
651                 logger.debug("Executing NXQL query: " + query.toString());
652             }
653
654             // If we have limit and/or offset, then pass true to get totalSize
655             // in returned DocumentModelList.
656                 Profiler profiler = new Profiler(this, 2);
657                 profiler.log("Executing NXQL query: " + query.toString());
658                 profiler.start();
659             if ((queryContext.getDocFilter().getOffset() > 0) || (queryContext.getDocFilter().getPageSize() > 0)) {
660                 docList = repoSession.query(query, null,
661                         queryContext.getDocFilter().getPageSize(), queryContext.getDocFilter().getOffset(), true);
662             } else {
663                 docList = repoSession.query(query);
664             }
665             profiler.stop();
666
667             //set repoSession to handle the document
668             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
669             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
670             handler.handle(Action.GET_ALL, wrapDoc);
671             handler.complete(Action.GET_ALL, wrapDoc);
672         } catch (DocumentException de) {
673             throw de;
674         } catch (Exception e) {
675             if (logger.isDebugEnabled()) {
676                 logger.debug("Caught exception ", e);
677             }
678             throw new DocumentException(e);
679         } finally {
680             if (repoSession != null) {
681                 releaseRepositorySession(repoSession);
682             }
683         }
684     }
685
686     /**
687      * update given document in the Nuxeo repository
688      *
689      * @param ctx service context under which this method is invoked
690      * @param id
691      *            of the document
692      * @param handler
693      *            should be used by the caller to provide and transform the
694      *            document
695      * @throws DocumentException
696      */
697     @Override
698     public void update(ServiceContext ctx, String id, DocumentHandler handler)
699             throws BadRequestException, DocumentNotFoundException,
700             DocumentException {
701         if (handler == null) {
702             throw new IllegalArgumentException(
703                     "RepositoryJavaClient.update: handler is missing");
704         }
705         RepositoryInstance repoSession = null;
706         try {
707             handler.prepare(Action.UPDATE);
708             repoSession = getRepositorySession();
709             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
710             DocumentModel doc = null;
711             try {
712                 doc = repoSession.getDocument(docRef);
713             } catch (ClientException ce) {
714                 String msg = "Could not find document to update with id=" + id;
715                 logger.error(msg, ce);
716                 throw new DocumentNotFoundException(msg, ce);
717             }
718             //set reposession to handle the document
719             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
720             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
721             handler.handle(Action.UPDATE, wrapDoc);
722             setCollectionSpaceCoreValues(ctx, doc, Action.UPDATE);
723             repoSession.saveDocument(doc);
724             repoSession.save();
725             handler.complete(Action.UPDATE, wrapDoc);
726         } catch (BadRequestException bre) {
727             throw bre;
728         } catch (DocumentException de) {
729             throw de;
730         } catch (Exception e) {
731             if (logger.isDebugEnabled()) {
732                 logger.debug("Caught exception ", e);
733             }
734             throw new DocumentException(e);
735         } finally {
736             if (repoSession != null) {
737                 releaseRepositorySession(repoSession);
738             }
739         }
740     }
741
742     /**
743      * delete a document from the Nuxeo repository
744      * @param ctx service context under which this method is invoked
745      * @param id
746      *            of the document
747      * @throws DocumentException
748      */
749     @Override
750     public void delete(ServiceContext ctx, String id) throws DocumentNotFoundException,
751             DocumentException {
752
753         if (logger.isDebugEnabled()) {
754             logger.debug("deleting document with id=" + id);
755         }
756         RepositoryInstance repoSession = null;
757         try {
758             repoSession = getRepositorySession();
759             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
760             try {
761                 repoSession.removeDocument(docRef);
762             } catch (ClientException ce) {
763                 String msg = "could not find document to delete with id=" + id;
764                 logger.error(msg, ce);
765                 throw new DocumentNotFoundException(msg, ce);
766             }
767             repoSession.save();
768         } catch (DocumentException de) {
769             throw de;
770         } catch (Exception e) {
771             if (logger.isDebugEnabled()) {
772                 logger.debug("Caught exception ", e);
773             }
774             throw new DocumentException(e);
775         } finally {
776             if (repoSession != null) {
777                 releaseRepositorySession(repoSession);
778             }
779         }
780     }
781
782     /* (non-Javadoc)
783      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
784      */
785     @Override
786     public void delete(ServiceContext ctx, String id, DocumentHandler handler)
787             throws DocumentNotFoundException, DocumentException {
788         throw new UnsupportedOperationException();
789     }
790
791     @Override
792     public Hashtable<String, String> retrieveWorkspaceIds(String domainName) throws Exception {
793         return NuxeoConnector.getInstance().retrieveWorkspaceIds(domainName);
794     }
795
796     @Override
797     public String createDomain(String domainName) throws Exception {
798         RepositoryInstance repoSession = null;
799         String domainId = null;
800         try {
801             repoSession = getRepositorySession();
802             DocumentRef parentDocRef = new PathRef("/");
803             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
804             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
805                     domainName, "Domain");
806             doc.setPropertyValue("dc:title", domainName);
807             doc.setPropertyValue("dc:description", "A CollectionSpace domain "
808                     + domainName);
809             doc = repoSession.createDocument(doc);
810             domainId = doc.getId();
811             repoSession.save();
812             if (logger.isDebugEnabled()) {
813                 logger.debug("created tenant domain name=" + domainName
814                         + " id=" + domainId);
815             }
816         } catch (Exception e) {
817             if (logger.isDebugEnabled()) {
818                 logger.debug("createTenantSpace caught exception ", e);
819             }
820             throw e;
821         } finally {
822             if (repoSession != null) {
823                 releaseRepositorySession(repoSession);
824             }
825         }
826         return domainId;
827     }
828
829     @Override
830     public String getDomainId(String domainName) throws Exception {
831         String domainId = null;
832         RepositoryInstance repoSession = null;
833         try {
834             repoSession = getRepositorySession();
835             DocumentRef docRef = new PathRef(
836                     "/" + domainName);
837             DocumentModel domain = repoSession.getDocument(docRef);
838             domainId = domain.getId();
839         } catch (Exception e) {
840             if (logger.isDebugEnabled()) {
841                 logger.debug("Caught exception ", e);
842             }
843             //there is no way to identify if document does not exist due to
844             //lack of typed exception for getDocument method
845             return null;
846         } finally {
847             if (repoSession != null) {
848                 releaseRepositorySession(repoSession);
849             }
850         }
851         return domainId;
852     }
853
854     /* (non-Javadoc)
855      * @see org.collectionspace.services.common.repository.RepositoryClient#createWorkspace(java.lang.String, java.lang.String)
856      */
857     @Override
858     public String createWorkspace(String domainName, String workspaceName) throws Exception {
859         RepositoryInstance repoSession = null;
860         String workspaceId = null;
861         try {
862             repoSession = getRepositorySession();
863             DocumentRef parentDocRef = new PathRef(
864                     "/" + domainName
865                     + "/" + "workspaces");
866             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
867             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
868                     workspaceName, "Workspace");
869             doc.setPropertyValue("dc:title", workspaceName);
870             doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
871                     + workspaceName);
872             doc = repoSession.createDocument(doc);
873             workspaceId = doc.getId();
874             repoSession.save();
875             if (logger.isDebugEnabled()) {
876                 logger.debug("created workspace name=" + workspaceName
877                         + " id=" + workspaceId);
878             }
879         } catch (Exception e) {
880             if (logger.isDebugEnabled()) {
881                 logger.debug("createWorkspace caught exception ", e);
882             }
883             throw e;
884         } finally {
885             if (repoSession != null) {
886                 releaseRepositorySession(repoSession);
887             }
888         }
889         return workspaceId;
890     }
891
892     /* (non-Javadoc)
893      * @see org.collectionspace.services.common.repository.RepositoryClient#getWorkspaceId(java.lang.String, java.lang.String)
894      */
895     @Override
896     public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
897         String workspaceId = null;
898         RepositoryInstance repoSession = null;
899         try {
900             repoSession = getRepositorySession();
901             DocumentRef docRef = new PathRef(
902                     "/" + tenantDomain
903                     + "/" + "workspaces"
904                     + "/" + workspaceName);
905             DocumentModel workspace = repoSession.getDocument(docRef);
906             workspaceId = workspace.getId();
907         } catch (DocumentException de) {
908             throw de;
909         } catch (Exception e) {
910             if (logger.isDebugEnabled()) {
911                 logger.debug("Caught exception ", e);
912             }
913             throw new DocumentException(e);
914         } finally {
915             if (repoSession != null) {
916                 releaseRepositorySession(repoSession);
917             }
918         }
919         return workspaceId;
920     }
921
922     /**
923      * Append a WHERE clause to the NXQL query.
924      *
925      * @param query         The NXQL query to which the WHERE clause will be appended.
926      * @param queryContext  The query context, which provides the WHERE clause to append.
927      */
928     private final void appendNXQLWhere(StringBuilder query, QueryContext queryContext) {
929         //
930         // Restrict search to a specific Nuxeo domain
931         // TODO This is a slow method for tenant-filter
932         // We should make this a property that is indexed.
933         //
934 //        query.append(" WHERE ecm:path STARTSWITH '/" + queryContext.domain + "'");
935
936         //
937         // Restrict search to the current tenant ID.  Is the domain path filter (above) still needed?
938         //
939         query.append(/*IQueryManager.SEARCH_QUALIFIER_AND +*/ " WHERE " + DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA + ":"
940                 + DocumentModelHandler.COLLECTIONSPACE_CORE_TENANTID
941                 + " = " + queryContext.getTenantId());
942         //
943         // Finally, append the incoming where clause
944         //
945         String whereClause = queryContext.getWhereClause();
946         if (whereClause != null && ! whereClause.trim().isEmpty()) {
947             // Due to an apparent bug/issue in how Nuxeo translates the NXQL query string
948             // into SQL, we need to parenthesize our 'where' clause
949             query.append(IQueryManager.SEARCH_QUALIFIER_AND + "(" + whereClause + ")");
950         }
951         //
952         // Please lookup this use in Nuxeo support and document here
953         //
954         query.append(IQueryManager.SEARCH_QUALIFIER_AND + "ecm:isProxy = 0");
955     }
956
957     /**
958      * Append an ORDER BY clause to the NXQL query.
959      *
960      * @param query         the NXQL query to which the ORDER BY clause will be appended.
961      * @param queryContext  the query context, which provides the ORDER BY clause to append.
962      *
963      * @throws DocumentException  if the supplied value of the orderBy clause is not valid.
964      *
965      */
966     private final void appendNXQLOrderBy(StringBuilder query, QueryContext queryContext)
967             throws Exception {
968         String orderByClause = queryContext.getOrderByClause();
969         if (orderByClause != null && ! orderByClause.trim().isEmpty()) {
970             if (isValidOrderByClause(orderByClause)) {
971                 query.append(" ORDER BY ");
972                 query.append(orderByClause);
973             } else {
974                 throw new DocumentException("Invalid format in sort request '" + orderByClause
975                         + "': must be schema_name:fieldName followed by optional sort order (' ASC' or ' DESC').");
976             }
977         }
978     }
979
980     /**
981      * Identifies whether the ORDER BY clause is valid.
982      *
983      * @param orderByClause the ORDER BY clause.
984      *
985      * @return              true if the ORDER BY clause is valid;
986      *                      false if it is not.
987      */
988     private final boolean isValidOrderByClause(String orderByClause) {
989         boolean isValidClause = false;
990         try {
991             Pattern orderByPattern = Pattern.compile(ORDER_BY_CLAUSE_REGEX);
992             Matcher orderByMatcher = orderByPattern.matcher(orderByClause);
993             if (orderByMatcher.matches()) {
994                 isValidClause = true;
995             }
996         } catch (PatternSyntaxException pe) {
997             logger.warn("ORDER BY clause regex pattern '" + ORDER_BY_CLAUSE_REGEX
998                     + "' could not be compiled: " + pe.getMessage());
999             // If reached, method will return a value of false.
1000         }
1001         return isValidClause;
1002     }
1003     
1004
1005     /**
1006      * Builds an NXQL SELECT query for a single document type.
1007      *
1008      * @param queryContext The query context
1009      * @return an NXQL query
1010      * @throws Exception if supplied values in the query are invalid.
1011      */
1012     private final String buildNXQLQuery(QueryContext queryContext) throws Exception {
1013         StringBuilder query = new StringBuilder("SELECT * FROM ");
1014         query.append(queryContext.getDocType());
1015         appendNXQLWhere(query, queryContext);
1016         appendNXQLOrderBy(query, queryContext);
1017         return query.toString();
1018     }
1019
1020     /**
1021      * Builds an NXQL SELECT query across multiple document types.
1022      *
1023      * @param docTypes     a list of document types to be queried
1024      * @param queryContext the query context
1025      * @return an NXQL query
1026      */
1027     private final String buildNXQLQuery(List<String> docTypes, QueryContext queryContext) {
1028         StringBuilder query = new StringBuilder("SELECT * FROM ");
1029         boolean fFirst = true;
1030         for (String docType : docTypes) {
1031             if (fFirst) {
1032                 fFirst = false;
1033             } else {
1034                 query.append(",");
1035             }
1036             query.append(docType);
1037         }
1038         appendNXQLWhere(query, queryContext);
1039         // FIXME add 'order by' clause here, if appropriate
1040         return query.toString();
1041     }
1042
1043     /**
1044      * Gets the repository session.
1045      *
1046      * @return the repository session
1047      * @throws Exception the exception
1048      */
1049     private RepositoryInstance getRepositorySession() throws Exception {
1050         // FIXME: is it possible to reuse repository session?
1051         // Authentication failures happen while trying to reuse the session
1052         Profiler profiler = new Profiler("getRepositorySession():", 2);
1053         profiler.start();
1054         NuxeoClient client = NuxeoConnector.getInstance().getClient();
1055         RepositoryInstance repoSession = client.openRepository();
1056         if (logger.isTraceEnabled()) {
1057             logger.debug("getRepository() repository root: " + repoSession.getRootDocument());
1058         }
1059         profiler.stop();
1060         return repoSession;
1061     }
1062
1063     /**
1064      * Release repository session.
1065      *
1066      * @param repoSession the repo session
1067      */
1068     private void releaseRepositorySession(RepositoryInstance repoSession) {
1069         try {
1070             NuxeoClient client = NuxeoConnector.getInstance().getClient();
1071             // release session
1072             client.releaseRepository(repoSession);
1073         } catch (Exception e) {
1074             logger.error("Could not close the repository session", e);
1075             // no need to throw this service specific exception
1076         }
1077     }
1078
1079 }