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