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