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