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