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