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