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