]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
19f7aeccd9555cedfa9b929ec7922d36b5563fdc
[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 DISTINCT hierarchy.id as id " // For debugging add: ", ltg.termdisplayname"
931                 + " FROM hierarchy "
932                 + " LEFT JOIN hierarchy h1 "
933                 + "   ON h1.parentid = hierarchy.id "
934                 + "   AND h1.name = 'locations_common:locTermGroupList' "
935                 + " LEFT JOIN loctermgroup ltg "
936                 + "   ON ltg.id = h1.id "
937                 + " WHERE (ltg.termdisplayname ILIKE '" + partialTerm + "%')";
938
939         // Make sure autocommit is off. See:
940         // http://jdbc.postgresql.org/documentation/80/query.html#query-with-cursor
941         // http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html
942         // connection.setAutoCommit(false);
943
944         // FIXME: Add exception handling and 'finally' blocks to ensure we close resources
945         // FIXME: Identify whether we can piggyback on existing JDBC method(s) in common.storage,
946         // and if so, add whatever additional functionality may be required to those method(s)
947         // FIXME: Add pagination handling
948
949         /*
950          Statement st = connection.createStatement();
951          // Enable use of the cursor for pagination
952          st.setFetchSize(50);
953          List<String> docIds = new ArrayList<String>();
954          ResultSet rs = st.executeQuery(sql);
955          String id;
956          while (rs.next()) {
957          id = rs.getString("id");
958          if (Tools.notBlank(id)) {
959          docIds.add(id);
960          }
961          }
962          rs.close();
963
964          // Close the statement.
965          st.close();
966          */
967
968         List<String> docIds = new ArrayList<String>();
969         CachedRowSet crs = null;
970         try {
971             crs = JDBCTools.executeQuery(dataSourceName, repositoryName, sql);
972
973             // If the response to the query is null or contains zero rows,
974             // return an empty list of document models
975             if (crs == null) {
976                 return result;
977             }
978             crs.last();
979             if (crs.getRow() == 0) {
980                 return result; // empty list of document models
981             }
982
983             // Otherwise, get the document IDs from the results of the query
984             String id;
985             /*
986             int idColumnIndex;
987             try {
988                 idColumnIndex = crs.findColumn(ID_COLUMN_NAME);
989             } catch (SQLException sqle) {
990                 logger.warn("Could not find expected column '" + ID_COLUMN_NAME + "' in query results.");
991                 return result; // return an empty list of document models
992             } finally {
993                 crs.close();
994             }
995             */
996             crs.beforeFirst();
997             while (crs.next()) {
998                 // id = crs.getString(idColumnIndex);
999                 id = crs.getString(1);
1000                 if (Tools.notBlank(id)) {
1001                     docIds.add(id);
1002                 }
1003             }
1004         } catch (SQLException sqle) {
1005             logger.warn("Could not obtain document IDs via SQL query '" + sql + "': " + sqle.getMessage());
1006             return result; // return an empty list of document models
1007         } finally {
1008             crs.close();
1009         } 
1010
1011         // Get a list of document models, using the IDs obtained from the query
1012         for (String docId : docIds) {
1013             result.add(NuxeoUtils.getDocumentModel(repoSession, docId));
1014         }
1015
1016         return result;
1017     }
1018
1019     private DocumentModelList getFilteredCMIS(RepositoryInstance repoSession, ServiceContext ctx, DocumentHandler handler, QueryContext queryContext)
1020             throws DocumentNotFoundException, DocumentException {
1021
1022         DocumentModelList result = new DocumentModelListImpl();
1023         try {
1024             String query = handler.getCMISQuery(queryContext);
1025
1026             DocumentFilter docFilter = handler.getDocumentFilter();
1027             int pageSize = docFilter.getPageSize();
1028             int offset = docFilter.getOffset();
1029             if (logger.isDebugEnabled()) {
1030                 logger.debug("Executing CMIS query: " + query.toString()
1031                         + "with pageSize: " + pageSize + " at offset: " + offset);
1032             }
1033
1034             // If we have limit and/or offset, then pass true to get totalSize
1035             // in returned DocumentModelList.
1036             Profiler profiler = new Profiler(this, 2);
1037             profiler.log("Executing CMIS query: " + query.toString());
1038             profiler.start();
1039             //
1040             IterableQueryResult queryResult = makeCMISQLQuery(repoSession, query, queryContext);
1041             try {
1042                 int totalSize = (int) queryResult.size();
1043                 ((DocumentModelListImpl) result).setTotalSize(totalSize);
1044                 // Skip the rows before our offset
1045                 if (offset > 0) {
1046                     queryResult.skipTo(offset);
1047                 }
1048                 int nRows = 0;
1049                 for (Map<String, Serializable> row : queryResult) {
1050                     if (logger.isTraceEnabled()) {
1051                         logger.trace(" Hierarchy Table ID is:" + row.get(IQueryManager.CMIS_TARGET_NUXEO_ID)
1052                                 + " nuxeo:pathSegment is: " + row.get(IQueryManager.CMIS_TARGET_NAME));
1053                     }
1054                     String nuxeoId = (String) row.get(IQueryManager.CMIS_TARGET_NUXEO_ID);
1055                     DocumentModel docModel = NuxeoUtils.getDocumentModel(repoSession, nuxeoId);
1056                     result.add(docModel);
1057                     nRows++;
1058                     if (nRows >= pageSize && pageSize != 0) { // A page size of zero means that they want all of them
1059                         logger.debug("Got page full of items - quitting");
1060                         break;
1061                     }
1062                 }
1063             } finally {
1064                 queryResult.close();
1065             }
1066             //
1067             profiler.stop();
1068
1069         } catch (Exception e) {
1070             if (logger.isDebugEnabled()) {
1071                 logger.debug("Caught exception ", e);
1072             }
1073             throw new DocumentException(e);
1074         }
1075
1076         //
1077         // Since we're not supporting paging yet for CMIS queries, we need to perform
1078         // a workaround for the paging information we return in our list of results
1079         //
1080         /*
1081          if (result != null) {
1082          docFilter.setStartPage(0);
1083          if (totalSize > docFilter.getPageSize()) {
1084          docFilter.setPageSize(totalSize);
1085          ((DocumentModelListImpl)result).setTotalSize(totalSize);
1086          }
1087          }
1088          */
1089
1090         return result;
1091     }
1092
1093     private String logException(Exception e, String msg) {
1094         String result = null;
1095
1096         String exceptionMessage = e.getMessage();
1097         exceptionMessage = exceptionMessage != null ? exceptionMessage : "<No details provided>";
1098         result = msg = msg + ". Caught exception:" + exceptionMessage;
1099
1100         if (logger.isTraceEnabled() == true) {
1101             logger.error(msg, e);
1102         } else {
1103             logger.error(msg);
1104         }
1105
1106         return result;
1107     }
1108
1109     /**
1110      * update given document in the Nuxeo repository
1111      *
1112      * @param ctx service context under which this method is invoked
1113      * @param csid of the document
1114      * @param handler should be used by the caller to provide and transform the
1115      * document
1116      * @throws BadRequestException
1117      * @throws DocumentNotFoundException
1118      * @throws TransactionException if the transaction times out or otherwise
1119      * cannot be successfully completed
1120      * @throws DocumentException
1121      */
1122     @Override
1123     public void update(ServiceContext ctx, String csid, DocumentHandler handler)
1124             throws BadRequestException, DocumentNotFoundException, TransactionException,
1125             DocumentException {
1126         if (handler == null) {
1127             throw new IllegalArgumentException(
1128                     "RepositoryJavaClient.update: document handler is missing.");
1129         }
1130
1131         RepositoryInstance repoSession = null;
1132         try {
1133             handler.prepare(Action.UPDATE);
1134             repoSession = getRepositorySession(ctx);
1135             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
1136             DocumentModel doc = null;
1137             try {
1138                 doc = repoSession.getDocument(docRef);
1139             } catch (ClientException ce) {
1140                 String msg = logException(ce, "Could not find document to update with CSID=" + csid);
1141                 throw new DocumentNotFoundException(msg, ce);
1142             }
1143             // Check for a versioned document, and check In and Out before we proceed.
1144             if (((DocumentModelHandler) handler).supportsVersioning()) {
1145                 /* Once we advance to 5.5 or later, we can add this. 
1146                  * See also https://jira.nuxeo.com/browse/NXP-8506
1147                  if(!doc.isVersionable()) {
1148                  throw new DocumentException("Configuration for: "
1149                  +handler.getServiceContextPath()+" supports versioning, but Nuxeo config does not!");
1150                  }
1151                  */
1152                 /* Force a version number - Not working. Apparently we need to configure the uid schema??
1153                  if(doc.getProperty("uid","major_version") == null) {
1154                  doc.setProperty("uid","major_version",1);
1155                  }
1156                  if(doc.getProperty("uid","minor_version") == null) {
1157                  doc.setProperty("uid","minor_version",0);
1158                  }
1159                  */
1160                 doc.checkIn(VersioningOption.MINOR, null);
1161                 doc.checkOut();
1162             }
1163
1164             //
1165             // Set reposession to handle the document
1166             //
1167             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1168             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
1169             handler.handle(Action.UPDATE, wrapDoc);
1170             repoSession.saveDocument(doc);
1171             repoSession.save();
1172             handler.complete(Action.UPDATE, wrapDoc);
1173         } catch (BadRequestException bre) {
1174             throw bre;
1175         } catch (DocumentException de) {
1176             throw de;
1177         } catch (WebApplicationException wae) {
1178             throw wae;
1179         } catch (Exception e) {
1180             if (logger.isDebugEnabled()) {
1181                 logger.debug("Caught exception ", e);
1182             }
1183             throw new DocumentException(e);
1184         } finally {
1185             if (repoSession != null) {
1186                 releaseRepositorySession(ctx, repoSession);
1187             }
1188         }
1189     }
1190
1191     /**
1192      * Save a documentModel to the Nuxeo repository.
1193      *
1194      * @param ctx service context under which this method is invoked
1195      * @param repoSession
1196      * @param docModel the document to save
1197      * @param fSaveSession if TRUE, will call CoreSession.save() to save
1198      * accumulated changes.
1199      * @throws ClientException
1200      * @throws DocumentException
1201      */
1202     public void saveDocWithoutHandlerProcessing(
1203             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1204             RepositoryInstance repoSession,
1205             DocumentModel docModel,
1206             boolean fSaveSession)
1207             throws ClientException, DocumentException {
1208
1209         try {
1210             repoSession.saveDocument(docModel);
1211             if (fSaveSession) {
1212                 repoSession.save();
1213             }
1214         } catch (ClientException ce) {
1215             throw ce;
1216         } catch (Exception e) {
1217             if (logger.isDebugEnabled()) {
1218                 logger.debug("Caught exception ", e);
1219             }
1220             throw new DocumentException(e);
1221         }
1222     }
1223
1224     /**
1225      * Save a list of documentModels to the Nuxeo repository.
1226      *
1227      * @param ctx service context under which this method is invoked
1228      * @param repoSession a repository session
1229      * @param docModelList a list of document models
1230      * @param fSaveSession if TRUE, will call CoreSession.save() to save
1231      * accumulated changes.
1232      * @throws ClientException
1233      * @throws DocumentException
1234      */
1235     public void saveDocListWithoutHandlerProcessing(
1236             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1237             RepositoryInstance repoSession,
1238             DocumentModelList docList,
1239             boolean fSaveSession)
1240             throws ClientException, DocumentException {
1241         try {
1242             DocumentModel[] docModelArray = new DocumentModel[docList.size()];
1243             repoSession.saveDocuments(docList.toArray(docModelArray));
1244             if (fSaveSession) {
1245                 repoSession.save();
1246             }
1247         } catch (ClientException ce) {
1248             throw ce;
1249         } catch (Exception e) {
1250             logger.error("Caught exception ", e);
1251             throw new DocumentException(e);
1252         }
1253     }
1254
1255     /**
1256      * delete a document from the Nuxeo repository
1257      *
1258      * @param ctx service context under which this method is invoked
1259      * @param id of the document
1260      * @throws DocumentException
1261      */
1262     @Override
1263     public void delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException,
1264             DocumentException, TransactionException {
1265         if (ctx == null) {
1266             throw new IllegalArgumentException(
1267                     "delete(ctx, ix, handler): ctx is missing");
1268         }
1269         if (handler == null) {
1270             throw new IllegalArgumentException(
1271                     "delete(ctx, ix, handler): handler is missing");
1272         }
1273         if (logger.isDebugEnabled()) {
1274             logger.debug("Deleting document with CSID=" + id);
1275         }
1276         RepositoryInstance repoSession = null;
1277         try {
1278             handler.prepare(Action.DELETE);
1279             repoSession = getRepositorySession(ctx);
1280             DocumentWrapper<DocumentModel> wrapDoc = null;
1281             try {
1282                 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
1283                 wrapDoc = new DocumentWrapperImpl<DocumentModel>(repoSession.getDocument(docRef));
1284                 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1285                 handler.handle(Action.DELETE, wrapDoc);
1286                 repoSession.removeDocument(docRef);
1287             } catch (ClientException ce) {
1288                 String msg = logException(ce, "Could not find document to delete with CSID=" + id);
1289                 throw new DocumentNotFoundException(msg, ce);
1290             }
1291             repoSession.save();
1292             handler.complete(Action.DELETE, wrapDoc);
1293         } catch (DocumentException de) {
1294             throw de;
1295         } catch (Exception e) {
1296             if (logger.isDebugEnabled()) {
1297                 logger.debug("Caught exception ", e);
1298             }
1299             throw new DocumentException(e);
1300         } finally {
1301             if (repoSession != null) {
1302                 releaseRepositorySession(ctx, repoSession);
1303             }
1304         }
1305     }
1306
1307     /* (non-Javadoc)
1308      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
1309      */
1310     @Override
1311     @Deprecated
1312     public void delete(@SuppressWarnings("rawtypes") ServiceContext ctx, String id)
1313             throws DocumentNotFoundException, DocumentException {
1314         throw new UnsupportedOperationException();
1315         // Use the other delete instead
1316     }
1317
1318     @Override
1319     public Hashtable<String, String> retrieveWorkspaceIds(RepositoryDomainType repoDomain) throws Exception {
1320         return NuxeoConnectorEmbedded.getInstance().retrieveWorkspaceIds(repoDomain);
1321     }
1322
1323     @Override
1324     public String createDomain(RepositoryDomainType repositoryDomain) throws Exception {
1325         RepositoryInstance repoSession = null;
1326         String domainId = null;
1327         try {
1328             //
1329             // Open a connection to the domain's repo/db
1330             //
1331             String repoName = repositoryDomain.getRepositoryName();
1332             repoSession = getRepositorySession(repoName); // domainName=storageName=repoName=databaseName
1333             //
1334             // First create the top-level domain directory
1335             //
1336             String domainName = repositoryDomain.getStorageName();
1337             DocumentRef parentDocRef = new PathRef("/");
1338             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
1339             DocumentModel domainDoc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
1340                     domainName, NUXEO_CORE_TYPE_DOMAIN);
1341             domainDoc.setPropertyValue("dc:title", domainName);
1342             domainDoc.setPropertyValue("dc:description", "A CollectionSpace domain "
1343                     + domainName);
1344             domainDoc = repoSession.createDocument(domainDoc);
1345             domainId = domainDoc.getId();
1346             repoSession.save();
1347             //
1348             // Next, create a "Workspaces" root directory to contain the workspace folders for the individual service documents
1349             //
1350             DocumentModel workspacesRoot = repoSession.createDocumentModel(domainDoc.getPathAsString(),
1351                     NuxeoUtils.Workspaces, NUXEO_CORE_TYPE_WORKSPACEROOT);
1352             workspacesRoot.setPropertyValue("dc:title", NuxeoUtils.Workspaces);
1353             workspacesRoot.setPropertyValue("dc:description", "A CollectionSpace workspaces directory for "
1354                     + domainDoc.getPathAsString());
1355             workspacesRoot = repoSession.createDocument(workspacesRoot);
1356             String workspacesRootId = workspacesRoot.getId();
1357             repoSession.save();
1358
1359             if (logger.isDebugEnabled()) {
1360                 logger.debug("Created tenant domain name=" + domainName
1361                         + " id=" + domainId + " "
1362                         + NuxeoUtils.Workspaces + " id=" + workspacesRootId);
1363                 logger.debug("Path to Domain: " + domainDoc.getPathAsString());
1364                 logger.debug("Path to Workspaces root: " + workspacesRoot.getPathAsString());
1365             }
1366         } catch (Exception e) {
1367             if (logger.isDebugEnabled()) {
1368                 logger.debug("Could not create tenant domain name=" + repositoryDomain.getStorageName() + " caught exception ", e);
1369             }
1370             throw e;
1371         } finally {
1372             if (repoSession != null) {
1373                 releaseRepositorySession(null, repoSession);
1374             }
1375         }
1376
1377         return domainId;
1378     }
1379
1380     @Override
1381     public String getDomainId(RepositoryDomainType repositoryDomain) throws Exception {
1382         String domainId = null;
1383         RepositoryInstance repoSession = null;
1384
1385         String repoName = repositoryDomain.getRepositoryName();
1386         String domainStorageName = repositoryDomain.getStorageName();
1387         if (domainStorageName != null && !domainStorageName.isEmpty()) {
1388             try {
1389                 repoSession = getRepositorySession(repoName);
1390                 DocumentRef docRef = new PathRef("/" + domainStorageName);
1391                 DocumentModel domain = repoSession.getDocument(docRef);
1392                 domainId = domain.getId();
1393             } catch (Exception e) {
1394                 if (logger.isTraceEnabled()) {
1395                     logger.trace("Caught exception ", e);  // The document doesn't exist, this let's us know we need to create it
1396                 }
1397                 //there is no way to identify if document does not exist due to
1398                 //lack of typed exception for getDocument method
1399                 return null;
1400             } finally {
1401                 if (repoSession != null) {
1402                     releaseRepositorySession(null, repoSession);
1403                 }
1404             }
1405         }
1406
1407         return domainId;
1408     }
1409
1410     /*
1411      * Returns the workspaces root directory for a given domain.
1412      */
1413     private DocumentModel getWorkspacesRoot(RepositoryInstance repoSession,
1414             String domainName) throws Exception {
1415         DocumentModel result = null;
1416
1417         String domainPath = "/" + domainName;
1418         DocumentRef parentDocRef = new PathRef(domainPath);
1419         DocumentModelList domainChildrenList = repoSession.getChildren(
1420                 parentDocRef);
1421         Iterator<DocumentModel> witer = domainChildrenList.iterator();
1422         while (witer.hasNext()) {
1423             DocumentModel childNode = witer.next();
1424             if (NuxeoUtils.Workspaces.equalsIgnoreCase(childNode.getName())) {
1425                 result = childNode;
1426                 logger.trace("Found workspaces directory at: " + result.getPathAsString());
1427                 break;
1428             }
1429         }
1430
1431         if (result == null) {
1432             throw new ClientException("Could not find workspace root directory in: "
1433                     + domainPath);
1434         }
1435
1436         return result;
1437     }
1438
1439     /* (non-Javadoc)
1440      * @see org.collectionspace.services.common.repository.RepositoryClient#createWorkspace(java.lang.String, java.lang.String)
1441      */
1442     @Override
1443     public String createWorkspace(RepositoryDomainType repositoryDomain, String workspaceName) throws Exception {
1444         RepositoryInstance repoSession = null;
1445         String workspaceId = null;
1446         try {
1447             String repoName = repositoryDomain.getRepositoryName();
1448             repoSession = getRepositorySession(repoName);
1449
1450             String domainStorageName = repositoryDomain.getStorageName();
1451             DocumentModel parentDoc = getWorkspacesRoot(repoSession, domainStorageName);
1452             if (logger.isTraceEnabled()) {
1453                 for (String facet : parentDoc.getFacets()) {
1454                     logger.trace("Facet: " + facet);
1455                 }
1456             }
1457
1458             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
1459                     workspaceName, NuxeoUtils.WORKSPACE_DOCUMENT_TYPE);
1460             doc.setPropertyValue("dc:title", workspaceName);
1461             doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
1462                     + workspaceName);
1463             doc = repoSession.createDocument(doc);
1464             workspaceId = doc.getId();
1465             repoSession.save();
1466             if (logger.isDebugEnabled()) {
1467                 logger.debug("Created workspace name=" + workspaceName
1468                         + " id=" + workspaceId);
1469             }
1470         } catch (Exception e) {
1471             if (logger.isDebugEnabled()) {
1472                 logger.debug("createWorkspace caught exception ", e);
1473             }
1474             throw e;
1475         } finally {
1476             if (repoSession != null) {
1477                 releaseRepositorySession(null, repoSession);
1478             }
1479         }
1480         return workspaceId;
1481     }
1482
1483     /* (non-Javadoc)
1484      * @see org.collectionspace.services.common.repository.RepositoryClient#getWorkspaceId(java.lang.String, java.lang.String)
1485      */
1486     @Override
1487     @Deprecated
1488     public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
1489         String workspaceId = null;
1490
1491         RepositoryInstance repoSession = null;
1492         try {
1493             repoSession = getRepositorySession((ServiceContext) null);
1494             DocumentRef docRef = new PathRef(
1495                     "/" + tenantDomain
1496                     + "/" + NuxeoUtils.Workspaces
1497                     + "/" + workspaceName);
1498             DocumentModel workspace = repoSession.getDocument(docRef);
1499             workspaceId = workspace.getId();
1500         } catch (DocumentException de) {
1501             throw de;
1502         } catch (Exception e) {
1503             if (logger.isDebugEnabled()) {
1504                 logger.debug("Caught exception ", e);
1505             }
1506             throw new DocumentException(e);
1507         } finally {
1508             if (repoSession != null) {
1509                 releaseRepositorySession(null, repoSession);
1510             }
1511         }
1512
1513         return workspaceId;
1514     }
1515
1516     public RepositoryInstance getRepositorySession(ServiceContext ctx) throws Exception {
1517         return getRepositorySession(ctx, ctx.getRepositoryName());
1518     }
1519
1520     public RepositoryInstance getRepositorySession(String repoName) throws Exception {
1521         return getRepositorySession(null, repoName);
1522     }
1523
1524     /**
1525      * Gets the repository session. - Package access only. If the 'ctx' param is
1526      * null then the repo name must be non-mull and vice-versa
1527      *
1528      * @return the repository session
1529      * @throws Exception the exception
1530      */
1531     public RepositoryInstance getRepositorySession(ServiceContext ctx, String repoName) throws Exception {
1532         RepositoryInstance repoSession = null;
1533
1534         Profiler profiler = new Profiler("getRepositorySession():", 2);
1535         profiler.start();
1536         //
1537         // To get a connection to the Nuxeo repo, we need either a valid ServiceContext instance or a repository name
1538         //
1539         if (ctx != null) {
1540             repoName = ctx.getRepositoryName(); // Notice we are overriding the passed in 'repoName' since we have a valid service context passed in to us
1541             repoSession = (RepositoryInstance) ctx.getCurrentRepositorySession(); // Look to see if one exists in the context before creating one
1542         } else if (repoName == null || repoName.trim().isEmpty()) {
1543             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.");
1544             logger.error(errMsg);
1545             throw new Exception(errMsg);
1546         }
1547         //
1548         // 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
1549         // just the repo name
1550         //
1551         if (repoSession == null) {
1552             NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
1553             repoSession = client.openRepository(repoName);
1554         } else {
1555             if (logger.isDebugEnabled() == true) {
1556                 logger.warn("Reusing the current context's repository session.");
1557             }
1558         }
1559
1560         if (logger.isTraceEnabled()) {
1561             logger.trace("Testing call to getRepository() repository root: " + repoSession.getRootDocument());
1562         }
1563
1564         profiler.stop();
1565
1566         if (ctx != null) {
1567             ctx.setCurrentRepositorySession(repoSession); // For reusing, save the repository session in the current service context
1568         }
1569
1570         return repoSession;
1571     }
1572
1573     /**
1574      * Release repository session. - Package access only.
1575      *
1576      * @param repoSession the repo session
1577      */
1578     public void releaseRepositorySession(ServiceContext ctx, RepositoryInstance repoSession) throws TransactionException {
1579         try {
1580             NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
1581             // release session
1582             if (ctx != null) {
1583                 ctx.clearCurrentRepositorySession(); //clear the current context of the now closed repo session
1584                 if (ctx.getCurrentRepositorySession() == null) {
1585                     client.releaseRepository(repoSession); //release the repo session if the service context's ref count is zeo.
1586                 }
1587             } else {
1588                 client.releaseRepository(repoSession); //repo session was acquired without a service context
1589             }
1590         } catch (TransactionRuntimeException tre) {
1591             TransactionException te = new TransactionException(tre);
1592             logger.error(te.getMessage(), tre); // Log the standard transaction exception message, plus an exception-specific stack trace
1593             throw te;
1594         } catch (Exception e) {
1595             logger.error("Could not close the repository session", e);
1596             // no need to throw this service specific exception
1597         }
1598     }
1599
1600     @Override
1601     public void doWorkflowTransition(ServiceContext ctx, String id,
1602             DocumentHandler handler, TransitionDef transitionDef)
1603             throws BadRequestException, DocumentNotFoundException,
1604             DocumentException {
1605         // 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
1606     }
1607 }