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