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