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