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