]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
427a8a9a207c9d37349bae95824a4a032388d4d2
[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     /**
573      * getFiltered get all documents for an entity service from the Document repository,
574      * given filter parameters specified by the handler. 
575      * @param ctx service context under which this method is invoked
576      * @param handler should be used by the caller to provide and transform the document
577      * @throws DocumentNotFoundException if workspace not found
578      * @throws DocumentException
579      */
580     @Override
581     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
582             throws DocumentNotFoundException, DocumentException {
583
584         DocumentFilter filter =  handler.getDocumentFilter();
585         String oldOrderBy = filter.getOrderByClause();
586         if (oldOrderBy!=null && oldOrderBy.isEmpty()){
587             filter.setOrderByClause("collectionspace_core:updatedAt DESC");  //per http://issues.collectionspace.org/browse/CSPACE-705
588         }
589         QueryContext queryContext = new QueryContext(ctx, handler);
590         RepositoryInstance repoSession = null;
591         try {
592             handler.prepare(Action.GET_ALL);
593             repoSession = getRepositorySession();
594             DocumentModelList docList = null;
595             String query = buildNXQLQuery(queryContext);
596
597             if (logger.isDebugEnabled()) {
598                 logger.debug("Executing NXQL query: " + query.toString());
599             }
600
601             // If we have limit and/or offset, then pass true to get totalSize
602             // in returned DocumentModelList.
603                 Profiler profiler = new Profiler(this, 2);
604                 profiler.log("Executing NXQL query: " + query.toString());
605                 profiler.start();
606             if ((queryContext.getDocFilter().getOffset() > 0) || (queryContext.getDocFilter().getPageSize() > 0)) {
607                 docList = repoSession.query(query, null,
608                         queryContext.getDocFilter().getPageSize(), queryContext.getDocFilter().getOffset(), true);
609             } else {
610                 docList = repoSession.query(query);
611             }
612             profiler.stop();
613
614             //set repoSession to handle the document
615             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
616             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
617             handler.handle(Action.GET_ALL, wrapDoc);
618             handler.complete(Action.GET_ALL, wrapDoc);
619         } catch (DocumentException de) {
620             throw de;
621         } catch (Exception e) {
622             if (logger.isDebugEnabled()) {
623                 logger.debug("Caught exception ", e);
624             }
625             throw new DocumentException(e);
626         } finally {
627             if (repoSession != null) {
628                 releaseRepositorySession(repoSession);
629             }
630         }
631     }
632
633     /**
634      * update given document in the Nuxeo repository
635      *
636      * @param ctx service context under which this method is invoked
637      * @param id
638      *            of the document
639      * @param handler
640      *            should be used by the caller to provide and transform the
641      *            document
642      * @throws DocumentException
643      */
644     @Override
645     public void update(ServiceContext ctx, String id, DocumentHandler handler)
646             throws BadRequestException, DocumentNotFoundException,
647             DocumentException {
648         if (handler == null) {
649             throw new IllegalArgumentException(
650                     "RepositoryJavaClient.update: handler is missing");
651         }
652         RepositoryInstance repoSession = null;
653         try {
654             handler.prepare(Action.UPDATE);
655             repoSession = getRepositorySession();
656             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
657             DocumentModel doc = null;
658             try {
659                 doc = repoSession.getDocument(docRef);
660             } catch (ClientException ce) {
661                 String msg = "Could not find document to update with id=" + id;
662                 logger.error(msg, ce);
663                 throw new DocumentNotFoundException(msg, ce);
664             }
665             //set reposession to handle the document
666             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
667             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
668             handler.handle(Action.UPDATE, wrapDoc);
669             setCollectionSpaceCoreValues(ctx, doc, Action.UPDATE);
670             repoSession.saveDocument(doc);
671             repoSession.save();
672             handler.complete(Action.UPDATE, wrapDoc);
673         } catch (BadRequestException bre) {
674             throw bre;
675         } catch (DocumentException de) {
676             throw de;
677         } catch (Exception e) {
678             if (logger.isDebugEnabled()) {
679                 logger.debug("Caught exception ", e);
680             }
681             throw new DocumentException(e);
682         } finally {
683             if (repoSession != null) {
684                 releaseRepositorySession(repoSession);
685             }
686         }
687     }
688
689     /**
690      * delete a document from the Nuxeo repository
691      * @param ctx service context under which this method is invoked
692      * @param id
693      *            of the document
694      * @throws DocumentException
695      */
696     @Override
697     public void delete(ServiceContext ctx, String id) throws DocumentNotFoundException,
698             DocumentException {
699
700         if (logger.isDebugEnabled()) {
701             logger.debug("deleting document with id=" + id);
702         }
703         RepositoryInstance repoSession = null;
704         try {
705             repoSession = getRepositorySession();
706             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
707             try {
708                 repoSession.removeDocument(docRef);
709             } catch (ClientException ce) {
710                 String msg = "could not find document to delete with id=" + id;
711                 logger.error(msg, ce);
712                 throw new DocumentNotFoundException(msg, ce);
713             }
714             repoSession.save();
715         } catch (DocumentException de) {
716             throw de;
717         } catch (Exception e) {
718             if (logger.isDebugEnabled()) {
719                 logger.debug("Caught exception ", e);
720             }
721             throw new DocumentException(e);
722         } finally {
723             if (repoSession != null) {
724                 releaseRepositorySession(repoSession);
725             }
726         }
727     }
728
729     /* (non-Javadoc)
730      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
731      */
732     @Override
733     public void delete(ServiceContext ctx, String id, DocumentHandler handler)
734             throws DocumentNotFoundException, DocumentException {
735         throw new UnsupportedOperationException();
736     }
737
738     @Override
739     public Hashtable<String, String> retrieveWorkspaceIds(String domainName) throws Exception {
740         return NuxeoConnector.getInstance().retrieveWorkspaceIds(domainName);
741     }
742
743     @Override
744     public String createDomain(String domainName) throws Exception {
745         RepositoryInstance repoSession = null;
746         String domainId = null;
747         try {
748             repoSession = getRepositorySession();
749             DocumentRef parentDocRef = new PathRef("/");
750             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
751             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
752                     domainName, "Domain");
753             doc.setPropertyValue("dc:title", domainName);
754             doc.setPropertyValue("dc:description", "A CollectionSpace domain "
755                     + domainName);
756             doc = repoSession.createDocument(doc);
757             domainId = doc.getId();
758             repoSession.save();
759             if (logger.isDebugEnabled()) {
760                 logger.debug("created tenant domain name=" + domainName
761                         + " id=" + domainId);
762             }
763         } catch (Exception e) {
764             if (logger.isDebugEnabled()) {
765                 logger.debug("createTenantSpace caught exception ", e);
766             }
767             throw e;
768         } finally {
769             if (repoSession != null) {
770                 releaseRepositorySession(repoSession);
771             }
772         }
773         return domainId;
774     }
775
776     @Override
777     public String getDomainId(String domainName) throws Exception {
778         String domainId = null;
779         RepositoryInstance repoSession = null;
780         try {
781             repoSession = getRepositorySession();
782             DocumentRef docRef = new PathRef(
783                     "/" + domainName);
784             DocumentModel domain = repoSession.getDocument(docRef);
785             domainId = domain.getId();
786         } catch (Exception e) {
787             if (logger.isDebugEnabled()) {
788                 logger.debug("Caught exception ", e);
789             }
790             //there is no way to identify if document does not exist due to
791             //lack of typed exception for getDocument method
792             return null;
793         } finally {
794             if (repoSession != null) {
795                 releaseRepositorySession(repoSession);
796             }
797         }
798         return domainId;
799     }
800
801     /* (non-Javadoc)
802      * @see org.collectionspace.services.common.repository.RepositoryClient#createWorkspace(java.lang.String, java.lang.String)
803      */
804     @Override
805     public String createWorkspace(String domainName, String workspaceName) throws Exception {
806         RepositoryInstance repoSession = null;
807         String workspaceId = null;
808         try {
809             repoSession = getRepositorySession();
810             DocumentRef parentDocRef = new PathRef(
811                     "/" + domainName
812                     + "/" + "workspaces");
813             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
814             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
815                     workspaceName, "Workspace");
816             doc.setPropertyValue("dc:title", workspaceName);
817             doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
818                     + workspaceName);
819             doc = repoSession.createDocument(doc);
820             workspaceId = doc.getId();
821             repoSession.save();
822             if (logger.isDebugEnabled()) {
823                 logger.debug("created workspace name=" + workspaceName
824                         + " id=" + workspaceId);
825             }
826         } catch (Exception e) {
827             if (logger.isDebugEnabled()) {
828                 logger.debug("createWorkspace caught exception ", e);
829             }
830             throw e;
831         } finally {
832             if (repoSession != null) {
833                 releaseRepositorySession(repoSession);
834             }
835         }
836         return workspaceId;
837     }
838
839     /* (non-Javadoc)
840      * @see org.collectionspace.services.common.repository.RepositoryClient#getWorkspaceId(java.lang.String, java.lang.String)
841      */
842     @Override
843     public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
844         String workspaceId = null;
845         RepositoryInstance repoSession = null;
846         try {
847             repoSession = getRepositorySession();
848             DocumentRef docRef = new PathRef(
849                     "/" + tenantDomain
850                     + "/" + "workspaces"
851                     + "/" + workspaceName);
852             DocumentModel workspace = repoSession.getDocument(docRef);
853             workspaceId = workspace.getId();
854         } catch (DocumentException de) {
855             throw de;
856         } catch (Exception e) {
857             if (logger.isDebugEnabled()) {
858                 logger.debug("Caught exception ", e);
859             }
860             throw new DocumentException(e);
861         } finally {
862             if (repoSession != null) {
863                 releaseRepositorySession(repoSession);
864             }
865         }
866         return workspaceId;
867     }
868
869     /**
870      * Append a WHERE clause to the NXQL query.
871      *
872      * @param query         The NXQL query to which the WHERE clause will be appended.
873      * @param queryContext  The query context, which provides the WHERE clause to append.
874      */
875     private final void appendNXQLWhere(StringBuilder query, QueryContext queryContext) {
876         //
877         // Restrict search to a specific Nuxeo domain
878         // TODO This is a slow method for tenant-filter
879         // We should make this a property that is indexed.
880         //
881 //        query.append(" WHERE ecm:path STARTSWITH '/" + queryContext.domain + "'");
882
883         //
884         // Restrict search to the current tenant ID.  Is the domain path filter (above) still needed?
885         //
886         query.append(/*IQueryManager.SEARCH_QUALIFIER_AND +*/ " WHERE " + DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA + ":"
887                 + DocumentModelHandler.COLLECTIONSPACE_CORE_TENANTID
888                 + " = " + queryContext.getTenantId());
889         //
890         // Finally, append the incoming where clause
891         //
892         String whereClause = queryContext.getWhereClause();
893         if (whereClause != null && ! whereClause.trim().isEmpty()) {
894             // Due to an apparent bug/issue in how Nuxeo translates the NXQL query string
895             // into SQL, we need to parenthesize our 'where' clause
896             query.append(IQueryManager.SEARCH_QUALIFIER_AND + "(" + whereClause + ")");
897         }
898         //
899         // Please lookup this use in Nuxeo support and document here
900         //
901         query.append(IQueryManager.SEARCH_QUALIFIER_AND + "ecm:isProxy = 0");
902     }
903
904     /**
905      * Append an ORDER BY clause to the NXQL query.
906      *
907      * @param query         the NXQL query to which the ORDER BY clause will be appended.
908      * @param queryContext  the query context, which provides the ORDER BY clause to append.
909      *
910      * @throws DocumentException  if the supplied value of the orderBy clause is not valid.
911      *
912      */
913     private final void appendNXQLOrderBy(StringBuilder query, QueryContext queryContext)
914             throws Exception {
915         String orderByClause = queryContext.getOrderByClause();
916         if (orderByClause != null && ! orderByClause.trim().isEmpty()) {
917             if (isValidOrderByClause(orderByClause)) {
918                 query.append(" ORDER BY ");
919                 query.append(orderByClause);
920             } else {
921                 throw new DocumentException("Invalid format in sort request '" + orderByClause
922                         + "': must be schema_name:fieldName followed by optional sort order (' ASC' or ' DESC').");
923             }
924         }
925     }
926
927     /**
928      * Identifies whether the ORDER BY clause is valid.
929      *
930      * @param orderByClause the ORDER BY clause.
931      *
932      * @return              true if the ORDER BY clause is valid;
933      *                      false if it is not.
934      */
935     private final boolean isValidOrderByClause(String orderByClause) {
936         boolean isValidClause = false;
937         try {
938             Pattern orderByPattern = Pattern.compile(ORDER_BY_CLAUSE_REGEX);
939             Matcher orderByMatcher = orderByPattern.matcher(orderByClause);
940             if (orderByMatcher.matches()) {
941                 isValidClause = true;
942             }
943         } catch (PatternSyntaxException pe) {
944             logger.warn("ORDER BY clause regex pattern '" + ORDER_BY_CLAUSE_REGEX
945                     + "' could not be compiled: " + pe.getMessage());
946             // If reached, method will return a value of false.
947         }
948         return isValidClause;
949     }
950     
951
952     /**
953      * Builds an NXQL SELECT query for a single document type.
954      *
955      * @param queryContext The query context
956      * @return an NXQL query
957      * @throws Exception if supplied values in the query are invalid.
958      */
959     private final String buildNXQLQuery(QueryContext queryContext) throws Exception {
960         StringBuilder query = new StringBuilder("SELECT * FROM ");
961         query.append(queryContext.getDocType());
962         appendNXQLWhere(query, queryContext);
963         appendNXQLOrderBy(query, queryContext);
964         return query.toString();
965     }
966
967     /**
968      * Builds an NXQL SELECT query across multiple document types.
969      *
970      * @param docTypes     a list of document types to be queried
971      * @param queryContext the query context
972      * @return an NXQL query
973      */
974     private final String buildNXQLQuery(List<String> docTypes, QueryContext queryContext) {
975         StringBuilder query = new StringBuilder("SELECT * FROM ");
976         boolean fFirst = true;
977         for (String docType : docTypes) {
978             if (fFirst) {
979                 fFirst = false;
980             } else {
981                 query.append(",");
982             }
983             query.append(docType);
984         }
985         appendNXQLWhere(query, queryContext);
986         // FIXME add 'order by' clause here, if appropriate
987         return query.toString();
988     }
989
990     /**
991      * Gets the repository session.
992      *
993      * @return the repository session
994      * @throws Exception the exception
995      */
996     private RepositoryInstance getRepositorySession() throws Exception {
997         // FIXME: is it possible to reuse repository session?
998         // Authentication failures happen while trying to reuse the session
999         Profiler profiler = new Profiler("getRepositorySession():", 2);
1000         profiler.start();
1001         NuxeoClient client = NuxeoConnector.getInstance().getClient();
1002         RepositoryInstance repoSession = client.openRepository();
1003         if (logger.isTraceEnabled()) {
1004             logger.debug("getRepository() repository root: " + repoSession.getRootDocument());
1005         }
1006         profiler.stop();
1007         return repoSession;
1008     }
1009
1010     /**
1011      * Release repository session.
1012      *
1013      * @param repoSession the repo session
1014      */
1015     private void releaseRepositorySession(RepositoryInstance repoSession) {
1016         try {
1017             NuxeoClient client = NuxeoConnector.getInstance().getClient();
1018             // release session
1019             client.releaseRepository(repoSession);
1020         } catch (Exception e) {
1021             logger.error("Could not close the repository session", e);
1022             // no need to throw this service specific exception
1023         }
1024     }
1025
1026 }