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