]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
781b0b58a4c1a6b2b6a065dbedbf74ab13d0a23a
[tmp/jakarta-migration.git] /
1 /**
2  * This document is a part of the source code and related artifacts for
3  * CollectionSpace, an open source collections management system for museums and
4  * related institutions:
5  *
6  * http://www.collectionspace.org http://wiki.collectionspace.org
7  *
8  * Copyright 2009 University of California at Berkeley
9  *
10  * Licensed under the Educational Community License (ECL), Version 2.0. You may
11  * not use this file except in compliance with this License.
12  *
13  * You may obtain a copy of the ECL 2.0 License at
14  *
15  * https://source.collectionspace.org/collection-space/LICENSE.txt
16  */
17 package org.collectionspace.services.nuxeo.client.java;
18
19 import java.io.Serializable;
20 import java.sql.Connection;
21 import java.sql.PreparedStatement;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.sql.Statement;
25 import java.util.ArrayList;
26 import java.util.Hashtable;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.UUID;
31 import javax.sql.rowset.CachedRowSet;
32
33 import javax.ws.rs.WebApplicationException;
34 import javax.ws.rs.core.MultivaluedMap;
35
36 import org.collectionspace.services.client.CollectionSpaceClient;
37 import org.collectionspace.services.client.IQueryManager;
38 import org.collectionspace.services.client.PoxPayloadIn;
39 import org.collectionspace.services.client.PoxPayloadOut;
40 import org.collectionspace.services.client.Profiler;
41 import org.collectionspace.services.client.workflow.WorkflowClient;
42 import org.collectionspace.services.common.context.ServiceContext;
43 import org.collectionspace.services.common.query.QueryContext;
44 import org.collectionspace.services.common.repository.RepositoryClient;
45 import org.collectionspace.services.common.storage.JDBCTools;
46 import org.collectionspace.services.common.storage.PreparedStatementSimpleBuilder;
47 import org.collectionspace.services.lifecycle.TransitionDef;
48 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
49
50 import org.collectionspace.services.common.document.BadRequestException;
51 import org.collectionspace.services.common.document.DocumentException;
52 import org.collectionspace.services.common.document.DocumentFilter;
53 import org.collectionspace.services.common.document.DocumentHandler;
54 import org.collectionspace.services.common.document.DocumentNotFoundException;
55 import org.collectionspace.services.common.document.DocumentHandler.Action;
56 import org.collectionspace.services.common.document.DocumentWrapper;
57 import org.collectionspace.services.common.document.DocumentWrapperImpl;
58 import org.collectionspace.services.common.document.TransactionException;
59 import org.collectionspace.services.config.tenant.RepositoryDomainType;
60
61 import org.nuxeo.common.utils.IdUtils;
62 import org.nuxeo.ecm.core.api.ClientException;
63 import org.nuxeo.ecm.core.api.DocumentModel;
64 import org.nuxeo.ecm.core.api.DocumentModelList;
65 import org.nuxeo.ecm.core.api.IterableQueryResult;
66 import org.nuxeo.ecm.core.api.VersioningOption;
67 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
68 import org.nuxeo.ecm.core.api.DocumentRef;
69 import org.nuxeo.ecm.core.api.IdRef;
70 import org.nuxeo.ecm.core.api.PathRef;
71 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
72 import org.nuxeo.runtime.transaction.TransactionRuntimeException;
73
74 //
75 // CSPACE-5036 - How to make CMISQL queries from Nuxeo
76 //
77 import org.apache.chemistry.opencmis.commons.server.CallContext;
78 import org.apache.chemistry.opencmis.server.impl.CallContextImpl;
79 import org.collectionspace.services.common.ServiceMain;
80 import org.collectionspace.services.common.api.Tools;
81 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
82 import org.collectionspace.services.common.config.TenantBindingUtils;
83 import org.collectionspace.services.config.tenant.TenantBindingType;
84 import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoCmisService;
85 import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoRepository;
86
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
89
90 /**
91  * RepositoryJavaClient is used to perform CRUD operations on documents in Nuxeo
92  * repository using Remote Java APIs. It uses
93  *
94  * @see DocumentHandler as IOHandler with the client.
95  *
96  * $LastChangedRevision: $ $LastChangedDate: $
97  */
98 public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn, PoxPayloadOut> {
99
100     /**
101      * The logger.
102      */
103     private final Logger logger = LoggerFactory.getLogger(RepositoryJavaClientImpl.class);
104 //    private final Logger profilerLogger = LoggerFactory.getLogger("remperf");
105 //    private String foo = Profiler.createLogger();
106     public static final String NUXEO_CORE_TYPE_DOMAIN = "Domain";
107     public static final String NUXEO_CORE_TYPE_WORKSPACEROOT = "WorkspaceRoot";
108     
109     /**
110      * Instantiates a new repository java client impl.
111      */
112     public RepositoryJavaClientImpl() {
113         //Empty constructor
114     }
115
116     public void assertWorkflowState(ServiceContext ctx,
117             DocumentModel docModel) throws DocumentNotFoundException, ClientException {
118         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
119         if (queryParams != null) {
120             //
121             // Look for the workflow "delete" query param and see if we need to assert that the
122             // docModel is in a non-deleted workflow state.
123             //
124             String currentState = docModel.getCurrentLifeCycleState();
125             String includeDeletedStr = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_NONDELETED);
126             boolean includeDeleted = includeDeletedStr == null ? true : Boolean.parseBoolean(includeDeletedStr);
127             if (includeDeleted == false) {
128                 //
129                 // We don't wanted soft-deleted object, so throw an exception if this one is soft-deleted.
130                 //
131                 if (currentState.equalsIgnoreCase(WorkflowClient.WORKFLOWSTATE_DELETED)) {
132                     String msg = "The GET assertion that docModel not be in 'deleted' workflow state failed.";
133                     logger.debug(msg);
134                     throw new DocumentNotFoundException(msg);
135                 }
136             }
137         }
138     }
139
140     /**
141      * create document in the Nuxeo repository
142      *
143      * @param ctx service context under which this method is invoked
144      * @param handler should be used by the caller to provide and transform the
145      * document
146      * @return id in repository of the newly created document
147      * @throws BadRequestException
148      * @throws TransactionException
149      * @throws DocumentException
150      */
151     @Override
152     public String create(ServiceContext ctx,
153             DocumentHandler handler) throws BadRequestException,
154             TransactionException, DocumentException {
155
156         String docType = NuxeoUtils.getTenantQualifiedDocType(ctx); //ctx.getDocumentType();
157         if (docType == null) {
158             throw new IllegalArgumentException(
159                     "RepositoryJavaClient.create: docType is missing");
160         }
161
162         if (handler == null) {
163             throw new IllegalArgumentException(
164                     "RepositoryJavaClient.create: handler is missing");
165         }
166         String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
167         if (nuxeoWspaceId == null) {
168             throw new DocumentNotFoundException(
169                     "Unable to find workspace for service " + ctx.getServiceName()
170                     + " check if the workspace exists in the Nuxeo repository");
171         }
172
173         RepositoryInstance repoSession = null;
174         try {
175             handler.prepare(Action.CREATE);
176             repoSession = getRepositorySession(ctx);
177             DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId);
178             DocumentModel wspaceDoc = repoSession.getDocument(nuxeoWspace);
179             String wspacePath = wspaceDoc.getPathAsString();
180             //give our own ID so PathRef could be constructed later on
181             String id = IdUtils.generateId(UUID.randomUUID().toString());
182             // create document model
183             DocumentModel doc = repoSession.createDocumentModel(wspacePath, id, docType);
184             /* Check for a versioned document, and check In and Out before we proceed.
185              * This does not work as we do not have the uid schema on our docs.
186              if(((DocumentModelHandler) handler).supportsVersioning()) {
187              doc.setProperty("uid","major_version",1);
188              doc.setProperty("uid","minor_version",0);
189              }
190              */
191             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
192             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
193             handler.handle(Action.CREATE, wrapDoc);
194             // create document with documentmodel
195             doc = repoSession.createDocument(doc);
196             repoSession.save();
197 // TODO for sub-docs need to call into the handler to let it deal with subitems. Pass in the id,
198 // and assume the handler has the state it needs (doc fragments). 
199             handler.complete(Action.CREATE, wrapDoc);
200             return id;
201         } catch (BadRequestException bre) {
202             throw bre;
203         } catch (Exception e) {
204             logger.error("Caught exception ", e);
205             throw new DocumentException(e);
206         } finally {
207             if (repoSession != null) {
208                 releaseRepositorySession(ctx, repoSession);
209             }
210         }
211
212     }
213
214     /**
215      * get document from the Nuxeo repository
216      *
217      * @param ctx service context under which this method is invoked
218      * @param id of the document to retrieve
219      * @param handler should be used by the caller to provide and transform the
220      * document
221      * @throws DocumentNotFoundException if the document cannot be found in the
222      * repository
223      * @throws TransactionException
224      * @throws DocumentException
225      */
226     @Override
227     public void get(ServiceContext ctx, String id, DocumentHandler handler)
228             throws DocumentNotFoundException, TransactionException, DocumentException {
229
230         if (handler == null) {
231             throw new IllegalArgumentException(
232                     "RepositoryJavaClient.get: handler is missing");
233         }
234
235         RepositoryInstance repoSession = null;
236         try {
237             handler.prepare(Action.GET);
238             repoSession = getRepositorySession(ctx);
239             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
240             DocumentModel docModel = null;
241             try {
242                 docModel = repoSession.getDocument(docRef);
243                 assertWorkflowState(ctx, docModel);
244             } catch (ClientException ce) {
245                 String msg = logException(ce, "Could not find document with CSID=" + id);
246                 throw new DocumentNotFoundException(msg, ce);
247             }
248             //
249             // Set repository session to handle the document
250             //
251             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
252             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(docModel);
253             handler.handle(Action.GET, wrapDoc);
254             handler.complete(Action.GET, wrapDoc);
255         } catch (IllegalArgumentException iae) {
256             throw iae;
257         } catch (DocumentException de) {
258             throw de;
259         } catch (Exception e) {
260             if (logger.isDebugEnabled()) {
261                 logger.debug("Caught exception ", e);
262             }
263             throw new DocumentException(e);
264         } finally {
265             if (repoSession != null) {
266                 releaseRepositorySession(ctx, repoSession);
267             }
268         }
269     }
270
271     /**
272      * get a document from the Nuxeo repository, using the docFilter params.
273      *
274      * @param ctx service context under which this method is invoked
275      * @param handler should be used by the caller to provide and transform the
276      * document. Handler must have a docFilter set to return a single item.
277      * @throws DocumentNotFoundException if the document cannot be found in the
278      * repository
279      * @throws TransactionException
280      * @throws DocumentException
281      */
282     @Override
283     public void get(ServiceContext ctx, DocumentHandler handler)
284             throws DocumentNotFoundException, TransactionException, DocumentException {
285         QueryContext queryContext = new QueryContext(ctx, handler);
286         RepositoryInstance repoSession = null;
287
288         try {
289             handler.prepare(Action.GET);
290             repoSession = getRepositorySession(ctx);
291
292             DocumentModelList docList = null;
293             // force limit to 1, and ignore totalSize
294             String query = NuxeoUtils.buildNXQLQuery(ctx, queryContext);
295             docList = repoSession.query(query, null, 1, 0, false);
296             if (docList.size() != 1) {
297                 throw new DocumentNotFoundException("No document found matching filter params: " + query);
298             }
299             DocumentModel doc = docList.get(0);
300
301             if (logger.isDebugEnabled()) {
302                 logger.debug("Executed NXQL query: " + query);
303             }
304
305             //set reposession to handle the document
306             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
307             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
308             handler.handle(Action.GET, wrapDoc);
309             handler.complete(Action.GET, wrapDoc);
310         } catch (IllegalArgumentException iae) {
311             throw iae;
312         } catch (DocumentException de) {
313             throw de;
314         } catch (Exception e) {
315             if (logger.isDebugEnabled()) {
316                 logger.debug("Caught exception ", e);
317             }
318             throw new DocumentException(e);
319         } finally {
320             if (repoSession != null) {
321                 releaseRepositorySession(ctx, repoSession);
322             }
323         }
324     }
325
326     public DocumentWrapper<DocumentModel> getDoc(
327             RepositoryInstance repoSession,
328             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
329             String csid) throws DocumentNotFoundException, DocumentException {
330         DocumentWrapper<DocumentModel> wrapDoc = null;
331
332         try {
333             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
334             DocumentModel doc = null;
335             try {
336                 doc = repoSession.getDocument(docRef);
337             } catch (ClientException ce) {
338                 String msg = logException(ce, "Could not find document with CSID=" + csid);
339                 throw new DocumentNotFoundException(msg, ce);
340             }
341             wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
342         } catch (IllegalArgumentException iae) {
343             throw iae;
344         } catch (DocumentException de) {
345             throw de;
346         }
347
348         return wrapDoc;
349     }
350
351     /**
352      * Get wrapped documentModel from the Nuxeo repository. The search is
353      * restricted to the workspace of the current context.
354      *
355      * @param ctx service context under which this method is invoked
356      * @param csid of the document to retrieve
357      * @throws DocumentNotFoundException
358      * @throws TransactionException
359      * @throws DocumentException
360      * @return a wrapped documentModel
361      */
362     @Override
363     public DocumentWrapper<DocumentModel> getDoc(
364             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
365             String csid) throws DocumentNotFoundException, TransactionException, DocumentException {
366         RepositoryInstance repoSession = null;
367         DocumentWrapper<DocumentModel> wrapDoc = null;
368
369         try {
370             // Open a new repository session
371             repoSession = getRepositorySession(ctx);
372             wrapDoc = getDoc(repoSession, ctx, csid);
373         } catch (IllegalArgumentException iae) {
374             throw iae;
375         } catch (DocumentException de) {
376             throw de;
377         } catch (Exception e) {
378             if (logger.isDebugEnabled()) {
379                 logger.debug("Caught exception ", e);
380             }
381             throw new DocumentException(e);
382         } finally {
383             if (repoSession != null) {
384                 releaseRepositorySession(ctx, repoSession);
385             }
386         }
387
388         if (logger.isWarnEnabled() == true) {
389             logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
390         }
391         return wrapDoc;
392     }
393
394     public DocumentWrapper<DocumentModel> findDoc(
395             RepositoryInstance repoSession,
396             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
397             String whereClause)
398             throws DocumentNotFoundException, DocumentException {
399         DocumentWrapper<DocumentModel> wrapDoc = null;
400
401         try {
402             QueryContext queryContext = new QueryContext(ctx, whereClause);
403             DocumentModelList docList = null;
404             // force limit to 1, and ignore totalSize
405             String query = NuxeoUtils.buildNXQLQuery(ctx, queryContext);
406             docList = repoSession.query(query,
407                     null, //Filter
408                     1, //limit
409                     0, //offset
410                     false); //countTotal
411             if (docList.size() != 1) {
412                 if (logger.isDebugEnabled()) {
413                     logger.debug("findDoc: Query found: " + docList.size() + " items.");
414                     logger.debug(" Query: " + query);
415                 }
416                 throw new DocumentNotFoundException("No document found matching filter params: " + query);
417             }
418             DocumentModel doc = docList.get(0);
419             wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
420         } catch (IllegalArgumentException iae) {
421             throw iae;
422         } catch (DocumentException de) {
423             throw de;
424         } catch (Exception e) {
425             if (logger.isDebugEnabled()) {
426                 logger.debug("Caught exception ", e);
427             }
428             throw new DocumentException(e);
429         }
430
431         return wrapDoc;
432     }
433
434     /**
435      * find wrapped documentModel from the Nuxeo repository
436      *
437      * @param ctx service context under which this method is invoked
438      * @param whereClause where NXQL where clause to get the document
439      * @throws DocumentNotFoundException
440      * @throws TransactionException
441      * @throws DocumentException
442      * @return a wrapped documentModel retrieved by the repository query
443      */
444     @Override
445     public DocumentWrapper<DocumentModel> findDoc(
446             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
447             String whereClause)
448             throws DocumentNotFoundException, TransactionException, DocumentException {
449         RepositoryInstance repoSession = null;
450         DocumentWrapper<DocumentModel> wrapDoc = null;
451
452         try {
453             repoSession = getRepositorySession(ctx);
454             wrapDoc = findDoc(repoSession, ctx, whereClause);
455         } catch (Exception e) {
456             throw new DocumentException("Unable to create a Nuxeo repository session.", e);
457         } finally {
458             if (repoSession != null) {
459                 releaseRepositorySession(ctx, repoSession);
460             }
461         }
462
463         if (logger.isWarnEnabled() == true) {
464             logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
465         }
466
467         return wrapDoc;
468     }
469
470     /**
471      * find doc and return CSID from the Nuxeo repository
472      *
473      * @param repoSession
474      * @param ctx service context under which this method is invoked
475      * @param whereClause where NXQL where clause to get the document
476      * @throws DocumentNotFoundException
477      * @throws TransactionException
478      * @throws DocumentException
479      * @return the CollectionSpace ID (CSID) of the requested document
480      */
481     @Override
482     public String findDocCSID(RepositoryInstance repoSession,
483             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String whereClause)
484             throws DocumentNotFoundException, TransactionException, DocumentException {
485         String csid = null;
486         boolean releaseSession = false;
487         try {
488             if (repoSession == null) {
489                 repoSession = this.getRepositorySession(ctx);
490                 releaseSession = true;
491             }
492             DocumentWrapper<DocumentModel> wrapDoc = findDoc(repoSession, ctx, whereClause);
493             DocumentModel docModel = wrapDoc.getWrappedObject();
494             csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
495         } catch (DocumentNotFoundException dnfe) {
496             throw dnfe;
497         } catch (IllegalArgumentException iae) {
498             throw iae;
499         } catch (DocumentException de) {
500             throw de;
501         } catch (Exception e) {
502             if (logger.isDebugEnabled()) {
503                 logger.debug("Caught exception ", e);
504             }
505             throw new DocumentException(e);
506         } finally {
507             if (releaseSession && (repoSession != null)) {
508                 this.releaseRepositorySession(ctx, repoSession);
509             }
510         }
511         return csid;
512     }
513
514     public DocumentWrapper<DocumentModelList> findDocs(
515             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
516             RepositoryInstance repoSession,
517             List<String> docTypes,
518             String whereClause,
519             String orderByClause,
520             int pageSize,
521             int pageNum,
522             boolean computeTotal)
523             throws DocumentNotFoundException, DocumentException {
524         DocumentWrapper<DocumentModelList> wrapDoc = null;
525
526         try {
527             if (docTypes == null || docTypes.size() < 1) {
528                 throw new DocumentNotFoundException(
529                         "The findDocs() method must specify at least one DocumentType.");
530             }
531             DocumentModelList docList = null;
532             QueryContext queryContext = new QueryContext(ctx, whereClause, orderByClause);
533             String query = NuxeoUtils.buildNXQLQuery(docTypes, queryContext);
534             if (logger.isDebugEnabled()) {
535                 logger.debug("findDocs() NXQL: " + query);
536             }
537             docList = repoSession.query(query, null, pageSize, pageSize * pageNum, computeTotal);
538             wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
539         } catch (IllegalArgumentException iae) {
540             throw iae;
541         } catch (Exception e) {
542             if (logger.isDebugEnabled()) {
543                 logger.debug("Caught exception ", e);
544             }
545             throw new DocumentException(e);
546         }
547
548         return wrapDoc;
549     }
550
551     protected static String buildInListForDocTypes(List<String> docTypes) {
552         StringBuilder sb = new StringBuilder();
553         sb.append("(");
554         boolean first = true;
555         for (String docType : docTypes) {
556             if (first) {
557                 first = false;
558             } else {
559                 sb.append(",");
560             }
561             sb.append("'");
562             sb.append(docType);
563             sb.append("'");
564         }
565         sb.append(")");
566         return sb.toString();
567     }
568
569     public DocumentWrapper<DocumentModelList> findDocs(
570             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
571             DocumentHandler handler,
572             RepositoryInstance repoSession,
573             List<String> docTypes)
574             throws DocumentNotFoundException, DocumentException {
575         DocumentWrapper<DocumentModelList> wrapDoc = null;
576
577         DocumentFilter filter = handler.getDocumentFilter();
578         String oldOrderBy = filter.getOrderByClause();
579         if (isClauseEmpty(oldOrderBy) == true) {
580             filter.setOrderByClause(DocumentFilter.ORDER_BY_LAST_UPDATED);
581         }
582         QueryContext queryContext = new QueryContext(ctx, handler);
583
584         try {
585             if (docTypes == null || docTypes.size() < 1) {
586                 throw new DocumentNotFoundException(
587                         "The findDocs() method must specify at least one DocumentType.");
588             }
589             DocumentModelList docList = null;
590             if (handler.isCMISQuery() == true) {
591                 String inList = buildInListForDocTypes(docTypes);
592                 ctx.getQueryParams().add(IQueryManager.SEARCH_RELATED_MATCH_OBJ_DOCTYPES, inList);
593                 docList = getFilteredCMIS(repoSession, ctx, handler, queryContext);
594             } else {
595                 String query = NuxeoUtils.buildNXQLQuery(docTypes, queryContext);
596                 if (logger.isDebugEnabled()) {
597                     logger.debug("findDocs() NXQL: " + query);
598                 }
599                 docList = repoSession.query(query, null, filter.getPageSize(), filter.getOffset(), true);
600             }
601             wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
602         } catch (IllegalArgumentException iae) {
603             throw iae;
604         } catch (Exception e) {
605             if (logger.isDebugEnabled()) {
606                 logger.debug("Caught exception ", e);
607             }
608             throw new DocumentException(e);
609         }
610
611         return wrapDoc;
612     }
613
614     /**
615      * Find a list of documentModels from the Nuxeo repository
616      *
617      * @param docTypes a list of DocType names to match
618      * @param whereClause where the clause to qualify on
619      * @throws DocumentNotFoundException
620      * @throws TransactionException
621      * @throws DocumentException
622      * @return a list of documentModels
623      */
624     @Override
625     public DocumentWrapper<DocumentModelList> findDocs(
626             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
627             List<String> docTypes,
628             String whereClause,
629             int pageSize, int pageNum, boolean computeTotal)
630             throws DocumentNotFoundException, TransactionException, DocumentException {
631         RepositoryInstance repoSession = null;
632         DocumentWrapper<DocumentModelList> wrapDoc = null;
633
634         try {
635             repoSession = getRepositorySession(ctx);
636             wrapDoc = findDocs(ctx, repoSession, docTypes, whereClause, null,
637                     pageSize, pageNum, computeTotal);
638         } catch (IllegalArgumentException iae) {
639             throw iae;
640         } catch (Exception e) {
641             if (logger.isDebugEnabled()) {
642                 logger.debug("Caught exception ", e);
643             }
644             throw new DocumentException(e);
645         } finally {
646             if (repoSession != null) {
647                 releaseRepositorySession(ctx, repoSession);
648             }
649         }
650
651         if (logger.isWarnEnabled() == true) {
652             logger.warn("Returned DocumentModelList instance was created with a repository session that is now closed.");
653         }
654
655         return wrapDoc;
656     }
657
658     /* (non-Javadoc)
659      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
660      */
661     @Override
662     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
663             throws DocumentNotFoundException, TransactionException, DocumentException {
664         if (handler == null) {
665             throw new IllegalArgumentException(
666                     "RepositoryJavaClient.getAll: handler is missing");
667         }
668
669         RepositoryInstance repoSession = null;
670         try {
671             handler.prepare(Action.GET_ALL);
672             repoSession = getRepositorySession(ctx);
673             DocumentModelList docModelList = new DocumentModelListImpl();
674             //FIXME: Should be using NuxeoUtils.createPathRef for security reasons
675             for (String csid : csidList) {
676                 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
677                 DocumentModel docModel = repoSession.getDocument(docRef);
678                 docModelList.add(docModel);
679             }
680
681             //set reposession to handle the document
682             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
683             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docModelList);
684             handler.handle(Action.GET_ALL, wrapDoc);
685             handler.complete(Action.GET_ALL, wrapDoc);
686         } catch (DocumentException de) {
687             throw de;
688         } catch (Exception e) {
689             if (logger.isDebugEnabled()) {
690                 logger.debug("Caught exception ", e);
691             }
692             throw new DocumentException(e);
693         } finally {
694             if (repoSession != null) {
695                 releaseRepositorySession(ctx, repoSession);
696             }
697         }
698     }
699
700     /**
701      * getAll get all documents for an entity entity service from the Nuxeo
702      * repository
703      *
704      * @param ctx service context under which this method is invoked
705      * @param handler should be used by the caller to provide and transform the
706      * document
707      * @throws DocumentNotFoundException
708      * @throws TransactionException
709      * @throws DocumentException
710      */
711     @Override
712     public void getAll(ServiceContext ctx, DocumentHandler handler)
713             throws DocumentNotFoundException, TransactionException, DocumentException {
714         if (handler == null) {
715             throw new IllegalArgumentException(
716                     "RepositoryJavaClient.getAll: handler is missing");
717         }
718         String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
719         if (nuxeoWspaceId == null) {
720             throw new DocumentNotFoundException(
721                     "Unable to find workspace for service "
722                     + ctx.getServiceName()
723                     + " check if the workspace exists in the Nuxeo repository.");
724         }
725
726         RepositoryInstance repoSession = null;
727         try {
728             handler.prepare(Action.GET_ALL);
729             repoSession = getRepositorySession(ctx);
730             DocumentRef wsDocRef = new IdRef(nuxeoWspaceId);
731             DocumentModelList docList = repoSession.getChildren(wsDocRef);
732             //set reposession to handle the document
733             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
734             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
735             handler.handle(Action.GET_ALL, wrapDoc);
736             handler.complete(Action.GET_ALL, wrapDoc);
737         } catch (DocumentException de) {
738             throw de;
739         } catch (Exception e) {
740             if (logger.isDebugEnabled()) {
741                 logger.debug("Caught exception ", e);
742             }
743             throw new DocumentException(e);
744         } finally {
745             if (repoSession != null) {
746                 releaseRepositorySession(ctx, repoSession);
747             }
748         }
749     }
750
751     private boolean isClauseEmpty(String theString) {
752         boolean result = true;
753         if (theString != null && !theString.isEmpty()) {
754             result = false;
755         }
756         return result;
757     }
758
759     public DocumentWrapper<DocumentModel> getDocFromCsid(
760             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
761             RepositoryInstance repoSession,
762             String csid)
763             throws Exception {
764         DocumentWrapper<DocumentModel> result = null;
765
766         result = new DocumentWrapperImpl(NuxeoUtils.getDocFromCsid(ctx, repoSession, csid));
767
768         return result;
769     }
770
771     /*
772      * A method to find a CollectionSpace document (of any type) given just a service context and
773      * its CSID.  A search across *all* service workspaces (within a given tenant context) is performed to find
774      * the document
775      * 
776      * This query searches Nuxeo's Hierarchy table where our CSIDs are stored in the "name" column.
777      */
778     @Override
779     public DocumentWrapper<DocumentModel> getDocFromCsid(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
780             String csid)
781             throws Exception {
782         DocumentWrapper<DocumentModel> result = null;
783         RepositoryInstance repoSession = null;
784         try {
785             repoSession = getRepositorySession(ctx);
786             result = getDocFromCsid(ctx, repoSession, csid);
787         } finally {
788             if (repoSession != null) {
789                 releaseRepositorySession(ctx, repoSession);
790             }
791         }
792
793         if (logger.isWarnEnabled() == true) {
794             logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
795         }
796
797         return result;
798     }
799
800     /**
801      * Returns a URI value for a document in the Nuxeo repository
802      *
803      * @param wrappedDoc a wrapped documentModel
804      * @throws ClientException
805      * @return a document URI
806      */
807     @Override
808     public String getDocURI(DocumentWrapper<DocumentModel> wrappedDoc) throws ClientException {
809         DocumentModel docModel = wrappedDoc.getWrappedObject();
810         String uri = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
811                 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
812         return uri;
813     }
814
815     /*
816      * See CSPACE-5036 - How to make CMISQL queries from Nuxeo
817      */
818     private IterableQueryResult makeCMISQLQuery(RepositoryInstance repoSession, String query, QueryContext queryContext) {
819         IterableQueryResult result = null;
820
821         // the NuxeoRepository should be constructed only once, then cached
822         // (its construction is expensive)
823         try {
824             NuxeoRepository repo = new NuxeoRepository(
825                     repoSession.getRepositoryName(), repoSession
826                     .getRootDocument().getId());
827             logger.debug("Repository ID:" + repo.getId() + " Root folder:"
828                     + repo.getRootFolderId());
829
830             CallContextImpl callContext = new CallContextImpl(
831                     CallContext.BINDING_LOCAL, repo.getId(), false);
832             callContext.put(CallContext.USERNAME, repoSession.getPrincipal()
833                     .getName());
834             NuxeoCmisService cmisService = new NuxeoCmisService(repo,
835                     callContext, repoSession);
836
837             result = repoSession.queryAndFetch(query, "CMISQL", cmisService);
838         } catch (ClientException e) {
839             // TODO Auto-generated catch block
840             logger.error("Encounter trouble making the following CMIS query: " + query, e);
841         }
842
843         return result;
844     }
845
846     /**
847      * getFiltered get all documents for an entity service from the Document
848      * repository, given filter parameters specified by the handler.
849      *
850      * @param ctx service context under which this method is invoked
851      * @param handler should be used by the caller to provide and transform the
852      * document
853      * @throws DocumentNotFoundException if workspace not found
854      * @throws TransactionException
855      * @throws DocumentException
856      */
857     @Override
858     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
859             throws DocumentNotFoundException, TransactionException, DocumentException {
860
861         DocumentFilter filter = handler.getDocumentFilter();
862         String oldOrderBy = filter.getOrderByClause();
863         if (isClauseEmpty(oldOrderBy) == true) {
864             filter.setOrderByClause(DocumentFilter.ORDER_BY_LAST_UPDATED);
865         }
866         QueryContext queryContext = new QueryContext(ctx, handler);
867
868         RepositoryInstance repoSession = null;
869         try {
870             handler.prepare(Action.GET_ALL);
871             repoSession = getRepositorySession(ctx); //Keeps a refcount here for the repository session so you need to release this when finished
872
873             DocumentModelList docList = null;
874             String query = NuxeoUtils.buildNXQLQuery(ctx, queryContext);
875
876             if (logger.isDebugEnabled()) {
877                 logger.debug("Executing NXQL query: " + query.toString());
878             }
879
880             // If we have limit and/or offset, then pass true to get totalSize
881             // in returned DocumentModelList.
882             Profiler profiler = new Profiler(this, 2);
883             profiler.log("Executing NXQL query: " + query.toString());
884             profiler.start();
885             if (handler.isJDBCQuery() == true) {
886                 docList = getFilteredJDBC(repoSession, ctx, handler, queryContext);
887             } else if (handler.isCMISQuery() == true) {
888                 docList = getFilteredCMIS(repoSession, ctx, handler, queryContext); //FIXME: REM - Need to deal with paging info in CMIS query
889             } else if ((queryContext.getDocFilter().getOffset() > 0) || (queryContext.getDocFilter().getPageSize() > 0)) {
890                 docList = repoSession.query(query, null,
891                         queryContext.getDocFilter().getPageSize(), queryContext.getDocFilter().getOffset(), true);
892             } else {
893                 docList = repoSession.query(query);
894             }
895             profiler.stop();
896
897             //set repoSession to handle the document
898             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
899             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
900             handler.handle(Action.GET_ALL, wrapDoc);
901             handler.complete(Action.GET_ALL, wrapDoc);
902         } catch (DocumentException de) {
903             throw de;
904         } catch (Exception e) {
905             if (logger.isDebugEnabled()) {
906                 logger.debug("Caught exception ", e);
907             }
908             throw new DocumentException(e);
909         } finally {
910             if (repoSession != null) {
911                 releaseRepositorySession(ctx, repoSession);
912             }
913         }
914     }
915
916     private DocumentModelList getFilteredJDBC(RepositoryInstance repoSession, ServiceContext ctx, 
917             DocumentHandler handler, QueryContext queryContext) throws Exception {
918         DocumentModelList result = new DocumentModelListImpl();
919         
920         final int LIMIT_SIZE = 40; // FIXME: Get from configuration and/or pagination query params
921
922         String dataSourceName = JDBCTools.NUXEO_DATASOURCE_NAME;
923         String repositoryName = ctx.getRepositoryName();
924         
925         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
926         String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
927         
928         // FIXME: Resolve how to handle the case where the partial term
929         // query parameter is included in the request, but has been given an
930         // empty (blank) value ("...?pt=") Then implement the required
931         // behavior here, if current behavior does not match what is required.
932         //
933         // (We're currently returning all records in that case.)
934        
935         // FIXME: Get all of the following values from appropriate external constants.
936         //
937         // At present, the two constants below are duplicated in both RepositoryJavaClientImpl
938         // and in AuthorityItemDocumentModelHandler.
939         final String TERM_GROUP_TABLE_NAME_PARAM = "TERM_GROUP_TABLE_NAME";
940         final String IN_AUTHORITY_PARAM = "IN_AUTHORITY";
941         // Get this from a constant in AuthorityResource or equivalent
942         final String PARENT_WILDCARD = "_ALL_"; 
943         
944         // Start with the default query
945         String selectStatement =
946                 "SELECT DISTINCT hierarchy_termgroup.id as id, hierarchy_termgroup.parentid as parentid"
947                 + " FROM " + handler.getJDBCQueryParams().get(TERM_GROUP_TABLE_NAME_PARAM) + " termgroup ";
948         
949         String joinClauses =
950                 " INNER JOIN hierarchy hierarchy_termgroup "
951                 + "  ON hierarchy_termgroup.id = termgroup.id "
952                 + " INNER JOIN hierarchy hierarchy_commonschema "
953                 + "   ON hierarchy_commonschema.id = hierarchy_termgroup.parentid ";
954
955         String whereClause =
956                 " WHERE (termgroup.termdisplayname ILIKE ?) ";
957         
958         String limitClause =
959                 " LIMIT " + LIMIT_SIZE;
960         
961         List<String> params = new ArrayList<>();
962
963         // Read tenant bindings configuration to determine whether
964         // to automatically insert leading, as well as trailing, wildcards
965         // into the term matching string.
966         TenantBindingConfigReaderImpl tReader =
967                 ServiceMain.getInstance().getTenantBindingConfigReader();
968         TenantBindingType tenantBinding = tReader.getTenantBinding(ctx.getTenantId());
969         String usesStartingWildcard = TenantBindingUtils.getPropertyValue(tenantBinding,
970                 IQueryManager.TENANT_USES_STARTING_WILDCARD_FOR_PARTIAL_TERM);
971         // Handle user-provided leading wildcard characters, in the
972         // configuration where a leading wildcard is not automatically inserted.
973         // (The user-provided wildcard must be in the first, or "starting"
974         // character position in the partial term value.)
975         if (Tools.notBlank(usesStartingWildcard) && usesStartingWildcard.equalsIgnoreCase(Boolean.FALSE.toString())) {
976             partialTerm = handleProvidedStartingWildcard(partialTerm);
977             // Otherwise, automatically insert a leading wildcard
978         } else {
979             partialTerm = JDBCTools.SQL_WILDCARD + partialTerm;
980         }
981         // Automatically insert a trailing wildcard
982         params.add(partialTerm + JDBCTools.SQL_WILDCARD); // Value for replaceable parameter 1 in the query
983         
984         // Restrict the query to filter out deleted records, if requested
985         String includeDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_NONDELETED);
986         if (includeDeleted != null && includeDeleted.equalsIgnoreCase(Boolean.FALSE.toString())) {
987             joinClauses = joinClauses
988                 + " INNER JOIN misc "
989                 + "   ON misc.id = hierarchy_commonschema.id ";
990             whereClause = whereClause
991                 + "   AND (misc.lifecyclestate <> '" + WorkflowClient.WORKFLOWSTATE_DELETED + "') ";
992         }
993
994         // If a particular authority is specified, restrict the query further
995         // to return only records within that authority
996         String inAuthorityValue = (String) handler.getJDBCQueryParams().get(IN_AUTHORITY_PARAM);
997         if (Tools.notBlank(inAuthorityValue)) {
998             // Handle the '_ALL_' case for inAuthority
999             if (inAuthorityValue.equals(PARENT_WILDCARD)) {
1000                 // Add nothing to the query here if it should match within all authorities
1001             } else {
1002                 joinClauses = joinClauses
1003                     + " INNER JOIN " + handler.getServiceContext().getCommonPartLabel() + " commonschema "
1004                     + "   ON commonschema.id = hierarchy_commonschema.id ";
1005                 whereClause = whereClause
1006                     + " AND (commonschema.inauthority = ?)";
1007                 params.add(inAuthorityValue); // Value for replaceable parameter 2 in the query
1008             }
1009         }
1010         
1011         String sql = selectStatement + joinClauses + whereClause + limitClause;
1012         
1013         // FIXME: Look into whether the following performance concern around
1014         // query planning with prepared statements may be affecting us:
1015         // http://stackoverflow.com/a/678452
1016         // If that proves to be a significant concern, we can instead use
1017         // JDBCTools.executeQuery(), and attempt to sanitize user input
1018         // against potential SQL injection attacks.
1019         PreparedStatementSimpleBuilder jdbcFilterQueryBuilder = new PreparedStatementSimpleBuilder(sql, params);
1020         
1021         List<String> docIds = new ArrayList<>();
1022         try (CachedRowSet crs = JDBCTools.executePreparedQuery(jdbcFilterQueryBuilder,
1023                 dataSourceName, repositoryName, sql)) {
1024
1025             // If the response to the query is null or contains zero rows,
1026             // return an empty list of document models
1027             if (crs == null) {
1028                 return result;
1029             }
1030             crs.last();
1031             if (crs.getRow() == 0) {
1032                 return result; // empty list of document models
1033             }
1034
1035             // Otherwise, get the document IDs from the results of the query
1036             String id;
1037             crs.beforeFirst();
1038             while (crs.next()) {
1039                 // Obtain parent IDs in the hierarchy table from column 2
1040                 // of results. These IDs correspond to parent (authority item)
1041                 // document records, rather than to term information group
1042                 // records.
1043                 id = crs.getString(2);
1044                 if (Tools.notBlank(id)) {
1045                     docIds.add(id);
1046                 }
1047             }
1048         } catch (SQLException sqle) {
1049             logger.warn("Could not obtain document IDs via SQL query '" + sql + "': " + sqle.getMessage());
1050             return result; // return an empty list of document models
1051         } 
1052
1053         // Get a list of document models, using the IDs obtained from the query
1054         DocumentModel docModel;
1055         for (String docId : docIds) {
1056             docModel = NuxeoUtils.getDocumentModel(repoSession, docId);
1057             if (docModel == null) {
1058                 logger.debug("Could not obtain document model for document with ID " + docId);
1059             } else {
1060                 result.add(NuxeoUtils.getDocumentModel(repoSession, docId));
1061             }
1062         }
1063
1064         return result;
1065     }
1066     
1067
1068     private DocumentModelList getFilteredCMIS(RepositoryInstance repoSession, ServiceContext ctx, DocumentHandler handler, QueryContext queryContext)
1069             throws DocumentNotFoundException, DocumentException {
1070
1071         DocumentModelList result = new DocumentModelListImpl();
1072         try {
1073             String query = handler.getCMISQuery(queryContext);
1074
1075             DocumentFilter docFilter = handler.getDocumentFilter();
1076             int pageSize = docFilter.getPageSize();
1077             int offset = docFilter.getOffset();
1078             if (logger.isDebugEnabled()) {
1079                 logger.debug("Executing CMIS query: " + query.toString()
1080                         + "with pageSize: " + pageSize + " at offset: " + offset);
1081             }
1082
1083             // If we have limit and/or offset, then pass true to get totalSize
1084             // in returned DocumentModelList.
1085             Profiler profiler = new Profiler(this, 2);
1086             profiler.log("Executing CMIS query: " + query.toString());
1087             profiler.start();
1088             //
1089             IterableQueryResult queryResult = makeCMISQLQuery(repoSession, query, queryContext);
1090             try {
1091                 int totalSize = (int) queryResult.size();
1092                 ((DocumentModelListImpl) result).setTotalSize(totalSize);
1093                 // Skip the rows before our offset
1094                 if (offset > 0) {
1095                     queryResult.skipTo(offset);
1096                 }
1097                 int nRows = 0;
1098                 for (Map<String, Serializable> row : queryResult) {
1099                     if (logger.isTraceEnabled()) {
1100                         logger.trace(" Hierarchy Table ID is:" + row.get(IQueryManager.CMIS_TARGET_NUXEO_ID)
1101                                 + " nuxeo:pathSegment is: " + row.get(IQueryManager.CMIS_TARGET_NAME));
1102                     }
1103                     String nuxeoId = (String) row.get(IQueryManager.CMIS_TARGET_NUXEO_ID);
1104                     DocumentModel docModel = NuxeoUtils.getDocumentModel(repoSession, nuxeoId);
1105                     result.add(docModel);
1106                     nRows++;
1107                     if (nRows >= pageSize && pageSize != 0) { // A page size of zero means that they want all of them
1108                         logger.debug("Got page full of items - quitting");
1109                         break;
1110                     }
1111                 }
1112             } finally {
1113                 queryResult.close();
1114             }
1115             //
1116             profiler.stop();
1117
1118         } catch (Exception e) {
1119             if (logger.isDebugEnabled()) {
1120                 logger.debug("Caught exception ", e);
1121             }
1122             throw new DocumentException(e);
1123         }
1124
1125         //
1126         // Since we're not supporting paging yet for CMIS queries, we need to perform
1127         // a workaround for the paging information we return in our list of results
1128         //
1129         /*
1130          if (result != null) {
1131          docFilter.setStartPage(0);
1132          if (totalSize > docFilter.getPageSize()) {
1133          docFilter.setPageSize(totalSize);
1134          ((DocumentModelListImpl)result).setTotalSize(totalSize);
1135          }
1136          }
1137          */
1138
1139         return result;
1140     }
1141
1142     private String logException(Exception e, String msg) {
1143         String result = null;
1144
1145         String exceptionMessage = e.getMessage();
1146         exceptionMessage = exceptionMessage != null ? exceptionMessage : "<No details provided>";
1147         result = msg = msg + ". Caught exception:" + exceptionMessage;
1148
1149         if (logger.isTraceEnabled() == true) {
1150             logger.error(msg, e);
1151         } else {
1152             logger.error(msg);
1153         }
1154
1155         return result;
1156     }
1157
1158     /**
1159      * update given document in the Nuxeo repository
1160      *
1161      * @param ctx service context under which this method is invoked
1162      * @param csid of the document
1163      * @param handler should be used by the caller to provide and transform the
1164      * document
1165      * @throws BadRequestException
1166      * @throws DocumentNotFoundException
1167      * @throws TransactionException if the transaction times out or otherwise
1168      * cannot be successfully completed
1169      * @throws DocumentException
1170      */
1171     @Override
1172     public void update(ServiceContext ctx, String csid, DocumentHandler handler)
1173             throws BadRequestException, DocumentNotFoundException, TransactionException,
1174             DocumentException {
1175         if (handler == null) {
1176             throw new IllegalArgumentException(
1177                     "RepositoryJavaClient.update: document handler is missing.");
1178         }
1179
1180         RepositoryInstance repoSession = null;
1181         try {
1182             handler.prepare(Action.UPDATE);
1183             repoSession = getRepositorySession(ctx);
1184             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
1185             DocumentModel doc = null;
1186             try {
1187                 doc = repoSession.getDocument(docRef);
1188             } catch (ClientException ce) {
1189                 String msg = logException(ce, "Could not find document to update with CSID=" + csid);
1190                 throw new DocumentNotFoundException(msg, ce);
1191             }
1192             // Check for a versioned document, and check In and Out before we proceed.
1193             if (((DocumentModelHandler) handler).supportsVersioning()) {
1194                 /* Once we advance to 5.5 or later, we can add this. 
1195                  * See also https://jira.nuxeo.com/browse/NXP-8506
1196                  if(!doc.isVersionable()) {
1197                  throw new DocumentException("Configuration for: "
1198                  +handler.getServiceContextPath()+" supports versioning, but Nuxeo config does not!");
1199                  }
1200                  */
1201                 /* Force a version number - Not working. Apparently we need to configure the uid schema??
1202                  if(doc.getProperty("uid","major_version") == null) {
1203                  doc.setProperty("uid","major_version",1);
1204                  }
1205                  if(doc.getProperty("uid","minor_version") == null) {
1206                  doc.setProperty("uid","minor_version",0);
1207                  }
1208                  */
1209                 doc.checkIn(VersioningOption.MINOR, null);
1210                 doc.checkOut();
1211             }
1212
1213             //
1214             // Set reposession to handle the document
1215             //
1216             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1217             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
1218             handler.handle(Action.UPDATE, wrapDoc);
1219             repoSession.saveDocument(doc);
1220             repoSession.save();
1221             handler.complete(Action.UPDATE, wrapDoc);
1222         } catch (BadRequestException bre) {
1223             throw bre;
1224         } catch (DocumentException de) {
1225             throw de;
1226         } catch (WebApplicationException wae) {
1227             throw wae;
1228         } catch (Exception e) {
1229             if (logger.isDebugEnabled()) {
1230                 logger.debug("Caught exception ", e);
1231             }
1232             throw new DocumentException(e);
1233         } finally {
1234             if (repoSession != null) {
1235                 releaseRepositorySession(ctx, repoSession);
1236             }
1237         }
1238     }
1239
1240     /**
1241      * Save a documentModel to the Nuxeo repository.
1242      *
1243      * @param ctx service context under which this method is invoked
1244      * @param repoSession
1245      * @param docModel the document to save
1246      * @param fSaveSession if TRUE, will call CoreSession.save() to save
1247      * accumulated changes.
1248      * @throws ClientException
1249      * @throws DocumentException
1250      */
1251     public void saveDocWithoutHandlerProcessing(
1252             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1253             RepositoryInstance repoSession,
1254             DocumentModel docModel,
1255             boolean fSaveSession)
1256             throws ClientException, DocumentException {
1257
1258         try {
1259             repoSession.saveDocument(docModel);
1260             if (fSaveSession) {
1261                 repoSession.save();
1262             }
1263         } catch (ClientException ce) {
1264             throw ce;
1265         } catch (Exception e) {
1266             if (logger.isDebugEnabled()) {
1267                 logger.debug("Caught exception ", e);
1268             }
1269             throw new DocumentException(e);
1270         }
1271     }
1272
1273     /**
1274      * Save a list of documentModels to the Nuxeo repository.
1275      *
1276      * @param ctx service context under which this method is invoked
1277      * @param repoSession a repository session
1278      * @param docModelList a list of document models
1279      * @param fSaveSession if TRUE, will call CoreSession.save() to save
1280      * accumulated changes.
1281      * @throws ClientException
1282      * @throws DocumentException
1283      */
1284     public void saveDocListWithoutHandlerProcessing(
1285             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1286             RepositoryInstance repoSession,
1287             DocumentModelList docList,
1288             boolean fSaveSession)
1289             throws ClientException, DocumentException {
1290         try {
1291             DocumentModel[] docModelArray = new DocumentModel[docList.size()];
1292             repoSession.saveDocuments(docList.toArray(docModelArray));
1293             if (fSaveSession) {
1294                 repoSession.save();
1295             }
1296         } catch (ClientException ce) {
1297             throw ce;
1298         } catch (Exception e) {
1299             logger.error("Caught exception ", e);
1300             throw new DocumentException(e);
1301         }
1302     }
1303
1304     /**
1305      * delete a document from the Nuxeo repository
1306      *
1307      * @param ctx service context under which this method is invoked
1308      * @param id of the document
1309      * @throws DocumentException
1310      */
1311     @Override
1312     public void delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException,
1313             DocumentException, TransactionException {
1314         if (ctx == null) {
1315             throw new IllegalArgumentException(
1316                     "delete(ctx, ix, handler): ctx is missing");
1317         }
1318         if (handler == null) {
1319             throw new IllegalArgumentException(
1320                     "delete(ctx, ix, handler): handler is missing");
1321         }
1322         if (logger.isDebugEnabled()) {
1323             logger.debug("Deleting document with CSID=" + id);
1324         }
1325         RepositoryInstance repoSession = null;
1326         try {
1327             handler.prepare(Action.DELETE);
1328             repoSession = getRepositorySession(ctx);
1329             DocumentWrapper<DocumentModel> wrapDoc = null;
1330             try {
1331                 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
1332                 wrapDoc = new DocumentWrapperImpl<DocumentModel>(repoSession.getDocument(docRef));
1333                 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1334                 handler.handle(Action.DELETE, wrapDoc);
1335                 repoSession.removeDocument(docRef);
1336             } catch (ClientException ce) {
1337                 String msg = logException(ce, "Could not find document to delete with CSID=" + id);
1338                 throw new DocumentNotFoundException(msg, ce);
1339             }
1340             repoSession.save();
1341             handler.complete(Action.DELETE, wrapDoc);
1342         } catch (DocumentException de) {
1343             throw de;
1344         } catch (Exception e) {
1345             if (logger.isDebugEnabled()) {
1346                 logger.debug("Caught exception ", e);
1347             }
1348             throw new DocumentException(e);
1349         } finally {
1350             if (repoSession != null) {
1351                 releaseRepositorySession(ctx, repoSession);
1352             }
1353         }
1354     }
1355
1356     /* (non-Javadoc)
1357      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
1358      */
1359     @Override
1360     @Deprecated
1361     public void delete(@SuppressWarnings("rawtypes") ServiceContext ctx, String id)
1362             throws DocumentNotFoundException, DocumentException {
1363         throw new UnsupportedOperationException();
1364         // Use the other delete instead
1365     }
1366
1367     @Override
1368     public Hashtable<String, String> retrieveWorkspaceIds(RepositoryDomainType repoDomain) throws Exception {
1369         return NuxeoConnectorEmbedded.getInstance().retrieveWorkspaceIds(repoDomain);
1370     }
1371
1372     @Override
1373     public String createDomain(RepositoryDomainType repositoryDomain) throws Exception {
1374         RepositoryInstance repoSession = null;
1375         String domainId = null;
1376         try {
1377             //
1378             // Open a connection to the domain's repo/db
1379             //
1380             String repoName = repositoryDomain.getRepositoryName();
1381             repoSession = getRepositorySession(repoName); // domainName=storageName=repoName=databaseName
1382             //
1383             // First create the top-level domain directory
1384             //
1385             String domainName = repositoryDomain.getStorageName();
1386             DocumentRef parentDocRef = new PathRef("/");
1387             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
1388             DocumentModel domainDoc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
1389                     domainName, NUXEO_CORE_TYPE_DOMAIN);
1390             domainDoc.setPropertyValue("dc:title", domainName);
1391             domainDoc.setPropertyValue("dc:description", "A CollectionSpace domain "
1392                     + domainName);
1393             domainDoc = repoSession.createDocument(domainDoc);
1394             domainId = domainDoc.getId();
1395             repoSession.save();
1396             //
1397             // Next, create a "Workspaces" root directory to contain the workspace folders for the individual service documents
1398             //
1399             DocumentModel workspacesRoot = repoSession.createDocumentModel(domainDoc.getPathAsString(),
1400                     NuxeoUtils.Workspaces, NUXEO_CORE_TYPE_WORKSPACEROOT);
1401             workspacesRoot.setPropertyValue("dc:title", NuxeoUtils.Workspaces);
1402             workspacesRoot.setPropertyValue("dc:description", "A CollectionSpace workspaces directory for "
1403                     + domainDoc.getPathAsString());
1404             workspacesRoot = repoSession.createDocument(workspacesRoot);
1405             String workspacesRootId = workspacesRoot.getId();
1406             repoSession.save();
1407
1408             if (logger.isDebugEnabled()) {
1409                 logger.debug("Created tenant domain name=" + domainName
1410                         + " id=" + domainId + " "
1411                         + NuxeoUtils.Workspaces + " id=" + workspacesRootId);
1412                 logger.debug("Path to Domain: " + domainDoc.getPathAsString());
1413                 logger.debug("Path to Workspaces root: " + workspacesRoot.getPathAsString());
1414             }
1415         } catch (Exception e) {
1416             if (logger.isDebugEnabled()) {
1417                 logger.debug("Could not create tenant domain name=" + repositoryDomain.getStorageName() + " caught exception ", e);
1418             }
1419             throw e;
1420         } finally {
1421             if (repoSession != null) {
1422                 releaseRepositorySession(null, repoSession);
1423             }
1424         }
1425
1426         return domainId;
1427     }
1428
1429     @Override
1430     public String getDomainId(RepositoryDomainType repositoryDomain) throws Exception {
1431         String domainId = null;
1432         RepositoryInstance repoSession = null;
1433
1434         String repoName = repositoryDomain.getRepositoryName();
1435         String domainStorageName = repositoryDomain.getStorageName();
1436         if (domainStorageName != null && !domainStorageName.isEmpty()) {
1437             try {
1438                 repoSession = getRepositorySession(repoName);
1439                 DocumentRef docRef = new PathRef("/" + domainStorageName);
1440                 DocumentModel domain = repoSession.getDocument(docRef);
1441                 domainId = domain.getId();
1442             } catch (Exception e) {
1443                 if (logger.isTraceEnabled()) {
1444                     logger.trace("Caught exception ", e);  // The document doesn't exist, this let's us know we need to create it
1445                 }
1446                 //there is no way to identify if document does not exist due to
1447                 //lack of typed exception for getDocument method
1448                 return null;
1449             } finally {
1450                 if (repoSession != null) {
1451                     releaseRepositorySession(null, repoSession);
1452                 }
1453             }
1454         }
1455
1456         return domainId;
1457     }
1458
1459     /*
1460      * Returns the workspaces root directory for a given domain.
1461      */
1462     private DocumentModel getWorkspacesRoot(RepositoryInstance repoSession,
1463             String domainName) throws Exception {
1464         DocumentModel result = null;
1465
1466         String domainPath = "/" + domainName;
1467         DocumentRef parentDocRef = new PathRef(domainPath);
1468         DocumentModelList domainChildrenList = repoSession.getChildren(
1469                 parentDocRef);
1470         Iterator<DocumentModel> witer = domainChildrenList.iterator();
1471         while (witer.hasNext()) {
1472             DocumentModel childNode = witer.next();
1473             if (NuxeoUtils.Workspaces.equalsIgnoreCase(childNode.getName())) {
1474                 result = childNode;
1475                 logger.trace("Found workspaces directory at: " + result.getPathAsString());
1476                 break;
1477             }
1478         }
1479
1480         if (result == null) {
1481             throw new ClientException("Could not find workspace root directory in: "
1482                     + domainPath);
1483         }
1484
1485         return result;
1486     }
1487
1488     /* (non-Javadoc)
1489      * @see org.collectionspace.services.common.repository.RepositoryClient#createWorkspace(java.lang.String, java.lang.String)
1490      */
1491     @Override
1492     public String createWorkspace(RepositoryDomainType repositoryDomain, String workspaceName) throws Exception {
1493         RepositoryInstance repoSession = null;
1494         String workspaceId = null;
1495         try {
1496             String repoName = repositoryDomain.getRepositoryName();
1497             repoSession = getRepositorySession(repoName);
1498
1499             String domainStorageName = repositoryDomain.getStorageName();
1500             DocumentModel parentDoc = getWorkspacesRoot(repoSession, domainStorageName);
1501             if (logger.isTraceEnabled()) {
1502                 for (String facet : parentDoc.getFacets()) {
1503                     logger.trace("Facet: " + facet);
1504                 }
1505             }
1506
1507             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
1508                     workspaceName, NuxeoUtils.WORKSPACE_DOCUMENT_TYPE);
1509             doc.setPropertyValue("dc:title", workspaceName);
1510             doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
1511                     + workspaceName);
1512             doc = repoSession.createDocument(doc);
1513             workspaceId = doc.getId();
1514             repoSession.save();
1515             if (logger.isDebugEnabled()) {
1516                 logger.debug("Created workspace name=" + workspaceName
1517                         + " id=" + workspaceId);
1518             }
1519         } catch (Exception e) {
1520             if (logger.isDebugEnabled()) {
1521                 logger.debug("createWorkspace caught exception ", e);
1522             }
1523             throw e;
1524         } finally {
1525             if (repoSession != null) {
1526                 releaseRepositorySession(null, repoSession);
1527             }
1528         }
1529         return workspaceId;
1530     }
1531
1532     /* (non-Javadoc)
1533      * @see org.collectionspace.services.common.repository.RepositoryClient#getWorkspaceId(java.lang.String, java.lang.String)
1534      */
1535     @Override
1536     @Deprecated
1537     public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
1538         String workspaceId = null;
1539
1540         RepositoryInstance repoSession = null;
1541         try {
1542             repoSession = getRepositorySession((ServiceContext) null);
1543             DocumentRef docRef = new PathRef(
1544                     "/" + tenantDomain
1545                     + "/" + NuxeoUtils.Workspaces
1546                     + "/" + workspaceName);
1547             DocumentModel workspace = repoSession.getDocument(docRef);
1548             workspaceId = workspace.getId();
1549         } catch (DocumentException de) {
1550             throw de;
1551         } catch (Exception e) {
1552             if (logger.isDebugEnabled()) {
1553                 logger.debug("Caught exception ", e);
1554             }
1555             throw new DocumentException(e);
1556         } finally {
1557             if (repoSession != null) {
1558                 releaseRepositorySession(null, repoSession);
1559             }
1560         }
1561
1562         return workspaceId;
1563     }
1564
1565     public RepositoryInstance getRepositorySession(ServiceContext ctx) throws Exception {
1566         return getRepositorySession(ctx, ctx.getRepositoryName());
1567     }
1568
1569     public RepositoryInstance getRepositorySession(String repoName) throws Exception {
1570         return getRepositorySession(null, repoName);
1571     }
1572
1573     /**
1574      * Gets the repository session. - Package access only. If the 'ctx' param is
1575      * null then the repo name must be non-mull and vice-versa
1576      *
1577      * @return the repository session
1578      * @throws Exception the exception
1579      */
1580     public RepositoryInstance getRepositorySession(ServiceContext ctx, String repoName) throws Exception {
1581         RepositoryInstance repoSession = null;
1582
1583         Profiler profiler = new Profiler("getRepositorySession():", 2);
1584         profiler.start();
1585         //
1586         // To get a connection to the Nuxeo repo, we need either a valid ServiceContext instance or a repository name
1587         //
1588         if (ctx != null) {
1589             repoName = ctx.getRepositoryName(); // Notice we are overriding the passed in 'repoName' since we have a valid service context passed in to us
1590             repoSession = (RepositoryInstance) ctx.getCurrentRepositorySession(); // Look to see if one exists in the context before creating one
1591         } else if (repoName == null || repoName.trim().isEmpty()) {
1592             String errMsg = String.format("We can't get a connection to the Nuxeo repo because the service context passed in was null and no repository name was passed in either.");
1593             logger.error(errMsg);
1594             throw new Exception(errMsg);
1595         }
1596         //
1597         // If we couldn't find a repoSession from the service context (or the context was null) then we need to create a new one using
1598         // just the repo name
1599         //
1600         if (repoSession == null) {
1601             NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
1602             repoSession = client.openRepository(repoName);
1603         } else {
1604             if (logger.isDebugEnabled() == true) {
1605                 logger.warn("Reusing the current context's repository session.");
1606             }
1607         }
1608
1609         if (logger.isTraceEnabled()) {
1610             logger.trace("Testing call to getRepository() repository root: " + repoSession.getRootDocument());
1611         }
1612
1613         profiler.stop();
1614
1615         if (ctx != null) {
1616             ctx.setCurrentRepositorySession(repoSession); // For reusing, save the repository session in the current service context
1617         }
1618
1619         return repoSession;
1620     }
1621
1622     /**
1623      * Release repository session. - Package access only.
1624      *
1625      * @param repoSession the repo session
1626      */
1627     public void releaseRepositorySession(ServiceContext ctx, RepositoryInstance repoSession) throws TransactionException {
1628         try {
1629             NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
1630             // release session
1631             if (ctx != null) {
1632                 ctx.clearCurrentRepositorySession(); //clear the current context of the now closed repo session
1633                 if (ctx.getCurrentRepositorySession() == null) {
1634                     client.releaseRepository(repoSession); //release the repo session if the service context's ref count is zeo.
1635                 }
1636             } else {
1637                 client.releaseRepository(repoSession); //repo session was acquired without a service context
1638             }
1639         } catch (TransactionRuntimeException tre) {
1640             TransactionException te = new TransactionException(tre);
1641             logger.error(te.getMessage(), tre); // Log the standard transaction exception message, plus an exception-specific stack trace
1642             throw te;
1643         } catch (Exception e) {
1644             logger.error("Could not close the repository session", e);
1645             // no need to throw this service specific exception
1646         }
1647     }
1648
1649     @Override
1650     public void doWorkflowTransition(ServiceContext ctx, String id,
1651             DocumentHandler handler, TransitionDef transitionDef)
1652             throws BadRequestException, DocumentNotFoundException,
1653             DocumentException {
1654         // This is a placeholder for when we change the StorageClient interface to treat workflow transitions as 1st class operations like 'get', 'create', 'update, 'delete', etc
1655     }
1656
1657     private String handleProvidedStartingWildcard(String partialTerm) {
1658         if (Tools.notBlank(partialTerm)) {
1659             // FIXME: Get this value from an existing constant, if available
1660             final String USER_SUPPLIED_WILDCARD = "*";
1661             if (partialTerm.substring(0, 1).equals(USER_SUPPLIED_WILDCARD)) {
1662                 StringBuffer buffer = new StringBuffer(partialTerm);
1663                 buffer.setCharAt(0, JDBCTools.SQL_WILDCARD.charAt(0));
1664                 partialTerm = buffer.toString();
1665             }
1666         }
1667         return partialTerm;
1668     }
1669 }