]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
db32494810dabc83692d142494d1d6bed65eeb72
[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.SQLException;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.HashSet;
25 import java.util.Hashtable;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.UUID;
31
32 import javax.sql.rowset.CachedRowSet;
33 import javax.ws.rs.core.MultivaluedMap;
34
35 //
36 // CSPACE-5036 - How to make CMISQL queries from Nuxeo
37 //
38 import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
39 import org.apache.chemistry.opencmis.commons.server.CallContext;
40 import org.apache.chemistry.opencmis.server.impl.CallContextImpl;
41 import org.apache.chemistry.opencmis.server.shared.ThresholdOutputStreamFactory;
42 import org.collectionspace.services.client.CollectionSpaceClient;
43 import org.collectionspace.services.client.IQueryManager;
44 import org.collectionspace.services.client.PoxPayloadIn;
45 import org.collectionspace.services.client.PoxPayloadOut;
46 import org.collectionspace.services.client.Profiler;
47 import org.collectionspace.services.client.index.IndexClient;
48 import org.collectionspace.services.client.workflow.WorkflowClient;
49 import org.collectionspace.services.common.CSWebApplicationException;
50 import org.collectionspace.services.common.ServiceMain;
51 import org.collectionspace.services.common.api.Tools;
52 import org.collectionspace.services.common.config.ConfigUtils;
53 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
54 import org.collectionspace.services.common.config.TenantBindingUtils;
55 import org.collectionspace.services.common.context.ServiceContext;
56 import org.collectionspace.services.common.document.BadRequestException;
57 import org.collectionspace.services.common.document.DocumentException;
58 import org.collectionspace.services.common.document.DocumentFilter;
59 import org.collectionspace.services.common.document.DocumentHandler;
60 import org.collectionspace.services.common.document.DocumentHandler.Action;
61 import org.collectionspace.services.common.document.DocumentNotFoundException;
62 import org.collectionspace.services.common.document.DocumentWrapper;
63 import org.collectionspace.services.common.document.DocumentWrapperImpl;
64 import org.collectionspace.services.common.document.TransactionException;
65 import org.collectionspace.services.common.query.QueryContext;
66 import org.collectionspace.services.common.repository.RepositoryClient;
67 import org.collectionspace.services.common.storage.JDBCTools;
68 import org.collectionspace.services.common.storage.PreparedStatementBuilder;
69 import org.collectionspace.services.common.storage.PreparedStatementSimpleBuilder;
70 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
71 import org.collectionspace.services.config.service.ServiceBindingType;
72 import org.collectionspace.services.config.tenant.RepositoryDomainType;
73 import org.collectionspace.services.config.tenant.TenantBindingType;
74 import org.collectionspace.services.lifecycle.TransitionDef;
75 import org.collectionspace.services.nuxeo.util.CSReindexFulltextRoot;
76 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
77 import org.nuxeo.common.utils.IdUtils;
78 import org.nuxeo.ecm.core.api.ClientException;
79 import org.nuxeo.ecm.core.api.DocumentModel;
80 import org.nuxeo.ecm.core.api.DocumentModelList;
81 import org.nuxeo.ecm.core.api.DocumentRef;
82 import org.nuxeo.ecm.core.api.IdRef;
83 import org.nuxeo.ecm.core.api.IterableQueryResult;
84 import org.nuxeo.ecm.core.api.PathRef;
85 import org.nuxeo.ecm.core.api.VersioningOption;
86 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
87 import org.nuxeo.ecm.core.opencmis.bindings.NuxeoCmisServiceFactory;
88 import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoCmisService;
89 import org.nuxeo.elasticsearch.ElasticSearchComponent;
90 import org.nuxeo.elasticsearch.api.ESClient;
91 import org.nuxeo.elasticsearch.api.ElasticSearchService;
92 import org.nuxeo.runtime.api.Framework;
93 import org.nuxeo.runtime.transaction.TransactionRuntimeException;
94 import org.slf4j.Logger;
95 import org.slf4j.LoggerFactory;
96
97 /**
98  * RepositoryClientImpl is used to perform CRUD operations on documents in Nuxeo
99  * repository using Remote Java APIs. It uses
100  *
101  * @see DocumentHandler as IOHandler with the client.
102  *
103  * $LastChangedRevision: $ $LastChangedDate: $
104  */
105 public class NuxeoRepositoryClientImpl implements RepositoryClient<PoxPayloadIn, PoxPayloadOut> {
106
107     /**
108      * The logger.
109      */
110     private final Logger logger = LoggerFactory.getLogger(NuxeoRepositoryClientImpl.class);
111 //    private final Logger profilerLogger = LoggerFactory.getLogger("remperf");
112 //    private String foo = Profiler.createLogger();
113     public static final String NUXEO_CORE_TYPE_DOMAIN = "Domain";
114     public static final String NUXEO_CORE_TYPE_WORKSPACEROOT = "WorkspaceRoot";
115     // FIXME: Get this value from an existing constant, if available
116     public static final String BACKSLASH = "\\";
117     public static final String USER_SUPPLIED_WILDCARD = "*";
118     public static final String USER_SUPPLIED_WILDCARD_REGEX = BACKSLASH + USER_SUPPLIED_WILDCARD;
119     public static final String USER_SUPPLIED_ANCHOR_CHAR = "^";
120     public static final String USER_SUPPLIED_ANCHOR_CHAR_REGEX = BACKSLASH + USER_SUPPLIED_ANCHOR_CHAR;
121     public static final String ENDING_ANCHOR_CHAR = "$";
122     public static final String ENDING_ANCHOR_CHAR_REGEX = BACKSLASH + ENDING_ANCHOR_CHAR;
123
124
125     /**
126      * Instantiates a new repository java client impl.
127      */
128     public NuxeoRepositoryClientImpl() {
129         //Empty constructor
130     }
131
132     public void assertWorkflowState(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, DocumentModel docModel) throws DocumentNotFoundException, ClientException {
133         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
134         if (queryParams != null) {
135             //
136             // Look for the workflow "delete" query param and see if we need to assert that the
137             // docModel is in a non-deleted workflow state.
138             //
139             String currentState = docModel.getCurrentLifeCycleState();
140             String includeDeletedStr = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_DELETED_QP);
141             boolean includeDeleted = (includeDeletedStr == null) ? true : Boolean.parseBoolean(includeDeletedStr);
142             if (includeDeleted == false) {
143                 //
144                 // We don't wanted soft-deleted objects, so throw an exception if this one is soft-deleted.
145                 //
146                 if (currentState.contains(WorkflowClient.WORKFLOWSTATE_DELETED)) {
147                     String msg = "The GET assertion that docModel not be in 'deleted' workflow state failed.";
148                     logger.debug(msg);
149                     throw new DocumentNotFoundException(msg);
150                 }
151             }
152         }
153     }
154
155     /**
156      * create document in the Nuxeo repository
157      *
158      * @param ctx service context under which this method is invoked
159      * @param handler should be used by the caller to provide and transform the
160      * document
161      * @return id in repository of the newly created document
162      * @throws BadRequestException
163      * @throws TransactionException
164      * @throws DocumentException
165      */
166     @Override
167     public String create(ServiceContext ctx,
168             DocumentHandler handler) throws BadRequestException,
169             TransactionException, DocumentException {
170
171         String docType = NuxeoUtils.getTenantQualifiedDocType(ctx); //ctx.getDocumentType();
172         if (docType == null) {
173             throw new IllegalArgumentException(
174                     "RepositoryJavaClient.create: docType is missing");
175         }
176
177         if (handler == null) {
178             throw new IllegalArgumentException(
179                     "RepositoryJavaClient.create: handler is missing");
180         }
181         String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
182         if (nuxeoWspaceId == null) {
183             throw new DocumentNotFoundException(
184                     "Unable to find workspace for service " + ctx.getServiceName()
185                     + " check if the workspace exists in the Nuxeo repository");
186         }
187
188         CoreSessionInterface repoSession = null;
189         try {
190             handler.prepare(Action.CREATE);
191             repoSession = getRepositorySession(ctx);
192             DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId);
193             DocumentModel wspaceDoc = repoSession.getDocument(nuxeoWspace);
194             String wspacePath = wspaceDoc.getPathAsString();
195             //give our own ID so PathRef could be constructed later on
196             String id = IdUtils.generateId(UUID.randomUUID().toString(), "-", true, 24);
197             // create document model
198             DocumentModel doc = repoSession.createDocumentModel(wspacePath, id, docType);
199             /* Check for a versioned document, and check In and Out before we proceed.
200              * This does not work as we do not have the uid schema on our docs.
201              if(((DocumentModelHandler) handler).supportsVersioning()) {
202              doc.setProperty("uid","major_version",1);
203              doc.setProperty("uid","minor_version",0);
204              }
205              */
206             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
207             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
208             handler.handle(Action.CREATE, wrapDoc);
209             // create document with documentmodel
210             doc = repoSession.createDocument(doc);
211             repoSession.save();
212                         // TODO for sub-docs need to call into the handler to let it deal with subitems. Pass in the id,
213                         // and assume the handler has the state it needs (doc fragments).
214             handler.complete(Action.CREATE, wrapDoc);
215             return id;
216         } catch (BadRequestException bre) {
217             if (ctx.isRollbackOnException()) {
218                 rollbackTransaction(repoSession);
219             }
220             throw bre;
221         } catch (Throwable e) {
222             if (ctx.isRollbackOnException()) {
223                 rollbackTransaction(repoSession);
224             }
225                 if (logger.isDebugEnabled()) {
226                         logger.debug("Call to low-level Nuxeo document create call failed: ", e);
227                 }
228             throw new NuxeoDocumentException(e);
229         } finally {
230             if (repoSession != null) {
231                 releaseRepositorySession(ctx, repoSession);
232             }
233         }
234     }
235
236         @Override
237     public boolean reindex(DocumentHandler handler, String indexid) throws DocumentNotFoundException, DocumentException
238     {
239         return reindex(handler, null, indexid);
240     }
241
242     @Override
243     public boolean reindex(DocumentHandler handler, String csid, String indexid) throws DocumentNotFoundException, DocumentException
244     {
245         boolean result = true;
246
247         switch (indexid) {
248                 case IndexClient.FULLTEXT_ID:
249                         result = reindexFulltext(handler, csid, indexid);
250                         break;
251                 case IndexClient.ELASTICSEARCH_ID:
252                         result = reindexElasticsearch(handler, csid, indexid);
253                         break;
254                 default:
255                         throw new NuxeoDocumentException(String.format("Unknown index '%s'.  Reindex request failed.",
256                                         indexid));
257         }
258
259         return result;
260     }
261
262     /**
263      * Reindex Nuxeo's fulltext index.
264      *
265      * @param handler
266      * @param csid
267      * @param indexid
268      * @return
269      * @throws NuxeoDocumentException
270      * @throws TransactionException
271      */
272     private boolean reindexFulltext(DocumentHandler handler, String csid, String indexid) throws NuxeoDocumentException, TransactionException {
273         boolean result = true;
274         CoreSessionInterface repoSession = null;
275         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = handler.getServiceContext();
276
277         try {
278             String queryString = handler.getDocumentsToIndexQuery(indexid, csid);
279             repoSession = getRepositorySession(ctx);
280             CSReindexFulltextRoot indexer = new CSReindexFulltextRoot(repoSession);
281             indexer.reindexFulltext(0, 0, queryString);
282         } catch (Throwable e) {
283             if (ctx.isRollbackOnException()) {
284                 rollbackTransaction(repoSession);
285             }
286             if (logger.isDebugEnabled()) {
287                 logger.debug("Caught exception trying to reindex Nuxeo repository ", e);
288             }
289             throw new NuxeoDocumentException(e);
290         } finally {
291             if (repoSession != null) {
292                 releaseRepositorySession(ctx, repoSession);
293             }
294         }
295
296         return result;
297     }
298
299     /**
300      * Reindex Nuxeo's Elasticsearch index.
301      *
302      * @param handler
303      * @param csid
304      * @param indexid
305      * @return
306      * @throws NuxeoDocumentException
307      * @throws TransactionException
308      */
309     private boolean reindexElasticsearch(DocumentHandler handler, String csid, String indexid) throws NuxeoDocumentException, TransactionException {
310         boolean result = false;
311
312         if (!Framework.isBooleanPropertyTrue("elasticsearch.enabled")) {
313             throw new NuxeoDocumentException("Request to reindex Elasticsearch failed because Elasticsearch is not enabled.");
314         }
315
316         CoreSessionInterface repoSession = null;
317         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = handler.getServiceContext();
318         ElasticSearchComponent es = (ElasticSearchComponent) Framework.getService(ElasticSearchService.class);
319         ESClient esClient = null;
320
321         try {
322             // Ensure an Elasticsearch connection has been established. This should have happened
323             // on startup, but may not have, if the Elasticsearch service wasn't reachable when
324             // Nuxeo started.
325
326             esClient = es.getClient();
327         } catch (Exception e) {
328             esClient = null;
329         }
330
331         try {
332             repoSession = getRepositorySession(ctx);
333
334             if (esClient == null) {
335                 // The connection to ES has not been established.
336                 // Attempt to start the ElasticSearchService.
337
338                 es.start(null);
339
340                 try {
341                     // Wait for startup requests to complete.
342
343                     Thread.sleep(10000);
344                 } catch (InterruptedException ex) {
345                 }
346             }
347
348             String repositoryName = repoSession.getRepositoryName();
349
350             logger.info(String.format("Rebuilding Elasticsearch index for repository %s", repositoryName));
351
352             es.dropAndInitRepositoryIndex(repositoryName);
353
354             TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
355             TenantBindingType tenantBinding = tReader.getTenantBinding(ctx.getTenantId());
356
357             for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
358                 Boolean isElasticsearchIndexed = serviceBinding.isElasticsearchIndexed();
359                 String servicesRepoDomainName = serviceBinding.getRepositoryDomain();
360
361                 if (isElasticsearchIndexed && servicesRepoDomainName != null && servicesRepoDomainName.trim().isEmpty() == false) {
362                     String docType = NuxeoUtils.getTenantQualifiedDocType(tenantBinding.getId(), serviceBinding.getObject().getName());
363                     String queryString = handler.getDocumentsToIndexQuery(indexid, docType, csid);
364
365                     logger.info(String.format("Starting Elasticsearch reindexing for docType %s in repository %s", docType, repositoryName));
366                     logger.debug(queryString);
367
368                     es.runReindexingWorker(repositoryName, queryString);
369                 }
370             }
371
372             result = true;
373         } catch (Throwable e) {
374             if (ctx.isRollbackOnException()) {
375                 rollbackTransaction(repoSession);
376             }
377
378             logger.error("Error reindexing Nuxeo repository", e);
379
380             throw new NuxeoDocumentException(e);
381         } finally {
382             if (repoSession != null) {
383                 releaseRepositorySession(ctx, repoSession);
384             }
385         }
386
387         return result;
388     }
389
390     @Override
391     public boolean synchronize(ServiceContext ctx, Object specifier, DocumentHandler handler)
392             throws DocumentNotFoundException, TransactionException, DocumentException {
393         boolean result = false;
394
395         if (handler == null) {
396             throw new IllegalArgumentException("RepositoryJavaClient.get: handler is missing");
397         }
398
399         CoreSessionInterface repoSession = null;
400         try {
401             handler.prepare(Action.SYNC);
402             repoSession = getRepositorySession(ctx);
403             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
404             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(specifier);
405             result = handler.handle(Action.SYNC, wrapDoc);
406             handler.complete(Action.SYNC, wrapDoc);
407         } catch (IllegalArgumentException iae) {
408             if (ctx.isRollbackOnException()) {
409                 rollbackTransaction(repoSession);
410             }
411             throw iae;
412         } catch (DocumentException de) {
413             if (ctx.isRollbackOnException()) {
414                 rollbackTransaction(repoSession);
415             }
416             throw de;
417         } catch (Throwable e) {
418             if (ctx.isRollbackOnException()) {
419                 rollbackTransaction(repoSession);
420             }
421             if (logger.isDebugEnabled()) {
422                 logger.debug("Caught exception ", e);
423             }
424             throw new NuxeoDocumentException(e);
425         } finally {
426             if (repoSession != null) {
427                 releaseRepositorySession(ctx, repoSession);
428             }
429         }
430
431         return result;
432     }
433
434     @Override
435     public boolean synchronizeItem(ServiceContext ctx, AuthorityItemSpecifier itemSpecifier, DocumentHandler handler)
436             throws DocumentNotFoundException, TransactionException, DocumentException {
437         boolean result = false;
438
439         if (handler == null) {
440             throw new IllegalArgumentException(
441                     "RepositoryJavaClient.get: handler is missing");
442         }
443
444         CoreSessionInterface repoSession = null;
445         try {
446             handler.prepare(Action.SYNC);
447             repoSession = getRepositorySession(ctx);
448             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
449             DocumentWrapper<AuthorityItemSpecifier> wrapDoc = new DocumentWrapperImpl<AuthorityItemSpecifier>(itemSpecifier);
450             result = handler.handle(Action.SYNC, wrapDoc);
451             handler.complete(Action.SYNC, wrapDoc);
452         } catch (IllegalArgumentException iae) {
453             if (ctx.isRollbackOnException()) {
454                 rollbackTransaction(repoSession);
455             }
456             throw iae;
457         } catch (DocumentException de) {
458             if (ctx.isRollbackOnException()) {
459                 rollbackTransaction(repoSession);
460             }
461             throw de;
462         } catch (Throwable e) {
463             if (ctx.isRollbackOnException()) {
464                 rollbackTransaction(repoSession);
465             }
466             throw new NuxeoDocumentException(e);
467         } finally {
468             if (repoSession != null) {
469                 releaseRepositorySession(ctx, repoSession);
470             }
471         }
472
473         return result;
474     }
475
476     /**
477      * get document from the Nuxeo repository
478      *
479      * @param ctx service context under which this method is invoked
480      * @param id of the document to retrieve
481      * @param handler should be used by the caller to provide and transform the
482      * document
483      * @throws DocumentNotFoundException if the document cannot be found in the
484      * repository
485      * @throws TransactionException
486      * @throws DocumentException
487      */
488         @Override
489     public void get(ServiceContext ctx, String id, DocumentHandler handler)
490             throws DocumentNotFoundException, TransactionException, DocumentException {
491
492         if (handler == null) {
493             throw new IllegalArgumentException(
494                     "RepositoryJavaClient.get: handler is missing");
495         }
496
497         CoreSessionInterface repoSession = null;
498         try {
499             handler.prepare(Action.GET);
500             repoSession = getRepositorySession(ctx);
501             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
502             DocumentModel docModel = null;
503             try {
504                 docModel = repoSession.getDocument(docRef);
505                 assertWorkflowState(ctx, docModel);
506             } catch (org.nuxeo.ecm.core.api.DocumentNotFoundException ce) {
507                 String msg = logException(ce,
508                                 String.format("Could not find %s resource/record with CSID=%s", ctx.getDocumentType(), id));
509                 throw new DocumentNotFoundException(msg, ce);
510             }
511             //
512             // Set repository session to handle the document
513             //
514             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
515             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(docModel);
516             handler.handle(Action.GET, wrapDoc);
517             handler.complete(Action.GET, wrapDoc);
518         } catch (IllegalArgumentException iae) {
519             throw iae;
520         } catch (DocumentException de) {
521                 if (logger.isDebugEnabled()) {
522                         logger.debug(de.getMessage(), de);
523                 }
524             throw de;
525         } catch (Throwable e) {
526             if (logger.isDebugEnabled()) {
527                 logger.debug("Caught exception ", e);
528             }
529             throw new NuxeoDocumentException(e);
530         } finally {
531             if (repoSession != null) {
532                 releaseRepositorySession(ctx, repoSession);
533             }
534         }
535     }
536
537     /**
538      * get a document from the Nuxeo repository, using the docFilter params.
539      *
540      * @param ctx service context under which this method is invoked
541      * @param handler should be used by the caller to provide and transform the
542      * document. Handler must have a docFilter set to return a single item.
543      * @throws DocumentNotFoundException if the document cannot be found in the
544      * repository
545      * @throws TransactionException
546      * @throws DocumentException
547      */
548     @Override
549     public void get(ServiceContext ctx, DocumentHandler handler)
550             throws DocumentNotFoundException, TransactionException, DocumentException {
551         QueryContext queryContext = new QueryContext(ctx, handler);
552         CoreSessionInterface repoSession = null;
553
554         try {
555             handler.prepare(Action.GET);
556             repoSession = getRepositorySession(ctx);
557
558             DocumentModelList docList = null;
559             // force limit to 1, and ignore totalSize
560             String query = NuxeoUtils.buildNXQLQuery(queryContext);
561             docList = repoSession.query(query, null, 1, 0, false);
562             if (docList.size() != 1) {
563                 throw new DocumentNotFoundException("No document found matching filter params: " + query);
564             }
565             DocumentModel doc = docList.get(0);
566
567             if (logger.isDebugEnabled()) {
568                 logger.debug("Executed NXQL query: " + query);
569             }
570
571             //set reposession to handle the document
572             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
573             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
574             handler.handle(Action.GET, wrapDoc);
575             handler.complete(Action.GET, wrapDoc);
576         } catch (IllegalArgumentException iae) {
577             throw iae;
578         } catch (DocumentException de) {
579             throw de;
580         } catch (Throwable e) {
581             if (logger.isDebugEnabled()) {
582                 logger.debug("Caught exception ", e);
583             }
584             throw new NuxeoDocumentException(e);
585         } finally {
586             if (repoSession != null) {
587                 releaseRepositorySession(ctx, repoSession);
588             }
589         }
590     }
591
592     public DocumentWrapper<DocumentModel> getDoc(
593                 CoreSessionInterface repoSession,
594             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
595             String csid) throws DocumentNotFoundException, DocumentException {
596         DocumentWrapper<DocumentModel> wrapDoc = null;
597
598         try {
599             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
600             DocumentModel doc = null;
601             try {
602                 doc = repoSession.getDocument(docRef);
603             } catch (ClientException ce) {
604                 String msg = logException(ce, "Could not find document with CSID=" + csid);
605                 throw new DocumentNotFoundException(msg, ce);
606             }
607             wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
608         } catch (IllegalArgumentException iae) {
609             throw iae;
610         } catch (DocumentException de) {
611             throw de;
612         }
613
614         return wrapDoc;
615     }
616
617     /**
618      * Get wrapped documentModel from the Nuxeo repository. The search is
619      * restricted to the workspace of the current context.
620      *
621      * @param ctx service context under which this method is invoked
622      * @param csid of the document to retrieve
623      * @throws DocumentNotFoundException
624      * @throws TransactionException
625      * @throws DocumentException
626      * @return a wrapped documentModel
627      */
628     @Override
629     public DocumentWrapper<DocumentModel> getDoc(
630             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
631             String csid) throws DocumentNotFoundException, TransactionException, DocumentException {
632         CoreSessionInterface repoSession = null;
633         DocumentWrapper<DocumentModel> wrapDoc = null;
634
635         try {
636             // Open a new repository session
637             repoSession = getRepositorySession(ctx);
638             wrapDoc = getDoc(repoSession, ctx, csid);
639         } catch (IllegalArgumentException iae) {
640             throw iae;
641         } catch (DocumentException de) {
642             throw de;
643         } catch (Exception e) {
644             if (logger.isDebugEnabled()) {
645                 logger.debug("Caught exception ", e);
646             }
647             throw new NuxeoDocumentException(e);
648         } finally {
649             if (repoSession != null) {
650                 releaseRepositorySession(ctx, repoSession);
651             }
652         }
653
654         if (logger.isWarnEnabled() == true) {
655             logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
656         }
657         return wrapDoc;
658     }
659
660     public DocumentWrapper<DocumentModel> findDoc(
661                 CoreSessionInterface repoSession,
662             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
663             String whereClause)
664             throws DocumentNotFoundException, DocumentException {
665         DocumentWrapper<DocumentModel> wrapDoc = null;
666
667         try {
668             QueryContext queryContext = new QueryContext(ctx, whereClause);
669             DocumentModelList docList = null;
670             // force limit to 1, and ignore totalSize
671             String query = NuxeoUtils.buildNXQLQuery(queryContext);
672             docList = repoSession.query(query,
673                     null, //Filter
674                     1, //limit
675                     0, //offset
676                     false); //countTotal
677             if (docList.size() != 1) {
678                 if (logger.isDebugEnabled()) {
679                     logger.debug("findDoc: Query found: " + docList.size() + " items.");
680                     logger.debug(" Query: " + query);
681                 }
682                 throw new DocumentNotFoundException("No document found matching filter params: " + query);
683             }
684             DocumentModel doc = docList.get(0);
685             wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
686         } catch (IllegalArgumentException iae) {
687             throw iae;
688         } catch (DocumentException de) {
689             throw de;
690         } catch (Exception e) {
691             if (logger.isDebugEnabled()) {
692                 logger.debug("Caught exception ", e);
693             }
694             throw new NuxeoDocumentException(e);
695         }
696
697         return wrapDoc;
698     }
699
700     /**
701      * find wrapped documentModel from the Nuxeo repository
702      *
703      * @param ctx service context under which this method is invoked
704      * @param whereClause where NXQL where clause to get the document
705      * @throws DocumentNotFoundException
706      * @throws TransactionException
707      * @throws DocumentException
708      * @return a wrapped documentModel retrieved by the repository query
709      */
710     @Override
711     public DocumentWrapper<DocumentModel> findDoc(
712             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
713             String whereClause)
714             throws DocumentNotFoundException, TransactionException, DocumentException {
715         CoreSessionInterface repoSession = null;
716         DocumentWrapper<DocumentModel> wrapDoc = null;
717
718         try {
719             repoSession = getRepositorySession(ctx);
720             wrapDoc = findDoc(repoSession, ctx, whereClause);
721         } catch (DocumentNotFoundException dnfe) {
722                 throw dnfe;
723         } catch (DocumentException de) {
724                 throw de;
725         } catch (Exception e) {
726                 if (repoSession == null) {
727                         throw new NuxeoDocumentException("Unable to create a Nuxeo repository session.", e);
728                 } else {
729                         throw new NuxeoDocumentException("Unexpected Nuxeo exception.", e);
730                 }
731         } finally {
732             if (repoSession != null) {
733                 releaseRepositorySession(ctx, repoSession);
734             }
735         }
736
737         if (logger.isWarnEnabled() == true) {
738             logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
739         }
740
741         return wrapDoc;
742     }
743
744     /**
745      * find doc and return CSID from the Nuxeo repository
746      *
747      * @param repoSession
748      * @param ctx service context under which this method is invoked
749      * @param whereClause where NXQL where clause to get the document
750      * @throws DocumentNotFoundException
751      * @throws TransactionException
752      * @throws DocumentException
753      * @return the CollectionSpace ID (CSID) of the requested document
754      */
755     @Override
756     public String findDocCSID(CoreSessionInterface repoSession,
757             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String whereClause)
758             throws DocumentNotFoundException, TransactionException, DocumentException {
759         String csid = null;
760         boolean releaseSession = false;
761         try {
762             if (repoSession == null) {
763                 repoSession = this.getRepositorySession(ctx);
764                 releaseSession = true;
765             }
766             DocumentWrapper<DocumentModel> wrapDoc = findDoc(repoSession, ctx, whereClause);
767             DocumentModel docModel = wrapDoc.getWrappedObject();
768             csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
769         } catch (DocumentNotFoundException dnfe) {
770             throw dnfe;
771         } catch (IllegalArgumentException iae) {
772             throw iae;
773         } catch (DocumentException de) {
774             throw de;
775         } catch (Exception e) {
776             if (logger.isDebugEnabled()) {
777                 logger.debug("Caught exception ", e);
778             }
779             throw new NuxeoDocumentException(e);
780         } finally {
781             if (releaseSession && (repoSession != null)) {
782                 this.releaseRepositorySession(ctx, repoSession);
783             }
784         }
785         return csid;
786     }
787
788     public DocumentWrapper<DocumentModelList> findDocs(
789             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
790             CoreSessionInterface repoSession,
791             List<String> docTypes,
792             String whereClause,
793             String orderByClause,
794             int pageNum,
795             int pageSize,
796             boolean useDefaultOrderByClause,
797             boolean computeTotal)
798             throws DocumentNotFoundException, DocumentException {
799         DocumentWrapper<DocumentModelList> wrapDoc = null;
800
801         try {
802             if (docTypes == null || docTypes.size() < 1) {
803                 throw new DocumentNotFoundException(
804                         "The findDocs() method must specify at least one DocumentType.");
805             }
806             DocumentModelList docList = null;
807             QueryContext queryContext = new QueryContext(ctx, whereClause, orderByClause);
808             String query = NuxeoUtils.buildNXQLQuery(docTypes, queryContext, useDefaultOrderByClause);
809             if (logger.isDebugEnabled()) {
810                 logger.debug("findDocs() NXQL: " + query);
811             }
812             docList = repoSession.query(query, null, pageSize, pageSize * pageNum, computeTotal);
813             wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
814         } catch (IllegalArgumentException iae) {
815             throw iae;
816         } catch (Exception e) {
817             if (logger.isDebugEnabled()) {
818                 logger.debug("Caught exception ", e);
819             }
820             throw new NuxeoDocumentException(e);
821         }
822
823         return wrapDoc;
824     }
825
826     protected static String buildInListForDocTypes(List<String> docTypes) {
827         StringBuilder sb = new StringBuilder();
828         sb.append("(");
829         boolean first = true;
830         for (String docType : docTypes) {
831             if (first) {
832                 first = false;
833             } else {
834                 sb.append(",");
835             }
836             sb.append("'");
837             sb.append(docType);
838             sb.append("'");
839         }
840         sb.append(")");
841         return sb.toString();
842     }
843
844     public DocumentWrapper<DocumentModelList> findDocs(
845             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
846             DocumentHandler handler,
847             CoreSessionInterface repoSession,
848             List<String> docTypes) throws DocumentNotFoundException, DocumentException {
849         DocumentWrapper<DocumentModelList> wrapDoc = null;
850
851         DocumentFilter filter = handler.getDocumentFilter();
852         String oldOrderBy = filter.getOrderByClause();
853         if (isClauseEmpty(oldOrderBy) == true) {
854             filter.setOrderByClause(DocumentFilter.ORDER_BY_LAST_UPDATED);
855         }
856         QueryContext queryContext = new QueryContext(ctx, handler);
857
858         try {
859             if (docTypes == null || docTypes.size() < 1) {
860                 throw new DocumentNotFoundException(
861                         "The findDocs() method must specify at least one DocumentType.");
862             }
863             DocumentModelList docList = null;
864             if (handler.isCMISQuery() == true) {
865                 String inList = buildInListForDocTypes(docTypes);
866                 ctx.getQueryParams().add(IQueryManager.SEARCH_RELATED_MATCH_OBJ_DOCTYPES, inList);
867                 if (isSubjectOrObjectQuery(ctx)) {
868                         docList = getFilteredCMISForSubjectOrObject(repoSession, ctx, handler, queryContext);
869                 } else {
870                         docList = getFilteredCMIS(repoSession, ctx, handler, queryContext);
871                 }
872             } else {
873                 String query = NuxeoUtils.buildNXQLQuery(docTypes, queryContext);
874                 if (logger.isDebugEnabled()) {
875                     logger.debug("findDocs() NXQL: " + query);
876                 }
877                 docList = repoSession.query(query, null, filter.getPageSize(), filter.getOffset(), true);
878             }
879             wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
880         } catch (IllegalArgumentException iae) {
881             throw iae;
882         } catch (Exception e) {
883             if (logger.isDebugEnabled()) {
884                 logger.debug("Caught exception ", e);
885             }
886             throw new NuxeoDocumentException(e);
887         }
888
889         return wrapDoc;
890     }
891
892     private DocumentModelList getFilteredCMISForSubjectOrObject(CoreSessionInterface repoSession,
893                         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, DocumentHandler handler, QueryContext queryContext) throws DocumentNotFoundException, DocumentException {
894         DocumentModelList result = null;
895
896         if (isSubjectOrObjectQuery(ctx) == true) {
897                 MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
898                 String asEitherCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_EITHER);
899
900                 queryParams.remove(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT);
901                 queryParams.remove(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT);
902
903                 //
904                 // First query for subjectCsid results.
905                 //
906                 queryParams.addFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT, asEitherCsid);
907             DocumentModelList subjectDocList = getFilteredCMIS(repoSession, ctx, handler, queryContext);
908             queryParams.remove(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT);
909
910                 //
911                 // Next query for objectCsid results.
912                 //
913                 queryParams.addFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT, asEitherCsid);
914             DocumentModelList objectDocList = getFilteredCMIS(repoSession, ctx, handler, queryContext);
915             queryParams.remove(IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT);
916
917             //
918             // Finally, combine the two results
919             //
920             result = mergeDocumentModelLists(subjectDocList, objectDocList);
921         }
922
923                 return result;
924         }
925
926         private DocumentModelList mergeDocumentModelLists(DocumentModelList subjectDocList,
927                         DocumentModelList objectDocList) {
928                 DocumentModelList result = null;
929
930                 if (subjectDocList == null || subjectDocList.isEmpty()) {
931                         return objectDocList;
932                 }
933
934                 if (objectDocList == null || objectDocList.isEmpty()) {
935                         return subjectDocList;
936                 }
937
938         result = new DocumentModelListImpl();
939
940         // Add the subject list
941                 Iterator<DocumentModel> iterator = subjectDocList.iterator();
942                 while (iterator.hasNext()) {
943                         DocumentModel dm = iterator.next();
944                         addToResults(result, dm);
945                 }
946
947                 // Add the object list
948                 iterator = objectDocList.iterator();
949                 while (iterator.hasNext()) {
950                         DocumentModel dm = iterator.next();
951                         addToResults(result, dm);
952                 }
953
954                 // Set the 'totalSize' value for book keeping sake
955                 ((DocumentModelListImpl) result).setTotalSize(result.size());
956
957                 return result;
958         }
959
960         //
961         // Only add if it is not already in the list
962         private void addToResults(DocumentModelList result, DocumentModel dm) {
963                 Iterator<DocumentModel> iterator = result.iterator();
964                 boolean found = false;
965
966                 while (iterator.hasNext()) {
967                         DocumentModel existingDm = iterator.next();
968                         if (existingDm.getId().equals(dm.getId())) {
969                                 found = true;
970                                 break;
971                         }
972                 }
973
974                 if (found == false) {
975                         result.add(dm);
976                 }
977         }
978
979         private boolean isSubjectOrObjectQuery(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
980         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
981         String asEitherCsid = (String)queryParams.getFirst(IQueryManager.SEARCH_RELATED_TO_CSID_AS_EITHER);
982         return asEitherCsid != null && !asEitherCsid.isEmpty();
983         }
984
985         /**
986      * Find a list of documentModels from the Nuxeo repository
987      *
988      * @param docTypes a list of DocType names to match
989      * @param whereClause where the clause to qualify on
990      * @throws DocumentNotFoundException
991      * @throws TransactionException
992      * @throws DocumentException
993      * @return a list of documentModels
994      */
995     @Override
996     public DocumentWrapper<DocumentModelList> findDocs(
997             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
998             List<String> docTypes,
999             String whereClause,
1000             int pageNum,
1001             int pageSize,
1002             boolean useDefaultOrderByClause,
1003             boolean computeTotal) throws DocumentNotFoundException, TransactionException, DocumentException {
1004         CoreSessionInterface repoSession = null;
1005         DocumentWrapper<DocumentModelList> wrapDoc = null;
1006
1007         try {
1008             repoSession = getRepositorySession(ctx);
1009             wrapDoc = findDocs(ctx,
1010                     repoSession,
1011                     docTypes,
1012                     whereClause,
1013                     null,
1014                     pageNum,
1015                     pageSize,
1016                     useDefaultOrderByClause,
1017                     computeTotal);
1018         } catch (IllegalArgumentException iae) {
1019             throw iae;
1020         } catch (Exception e) {
1021             if (logger.isDebugEnabled()) {
1022                 logger.debug("Caught exception ", e);
1023             }
1024             throw new NuxeoDocumentException(e);
1025         } finally {
1026             if (repoSession != null) {
1027                 releaseRepositorySession(ctx, repoSession);
1028             }
1029         }
1030
1031         if (logger.isWarnEnabled() == true) {
1032             logger.warn("Returned DocumentModelList instance was created with a repository session that is now closed.");
1033         }
1034
1035         return wrapDoc;
1036     }
1037
1038     /* (non-Javadoc)
1039      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
1040      */
1041     @Override
1042     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
1043             throws DocumentNotFoundException, TransactionException, DocumentException {
1044         if (handler == null) {
1045             throw new IllegalArgumentException(
1046                     "RepositoryJavaClient.getAll: handler is missing");
1047         }
1048
1049         CoreSessionInterface repoSession = null;
1050         try {
1051             handler.prepare(Action.GET_ALL);
1052             repoSession = getRepositorySession(ctx);
1053             DocumentModelList docModelList = new DocumentModelListImpl();
1054             //FIXME: Should be using NuxeoUtils.createPathRef for security reasons
1055             for (String csid : csidList) {
1056                 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
1057                 DocumentModel docModel = repoSession.getDocument(docRef);
1058                 docModelList.add(docModel);
1059             }
1060
1061             //set reposession to handle the document
1062             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1063             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docModelList);
1064             handler.handle(Action.GET_ALL, wrapDoc);
1065             handler.complete(Action.GET_ALL, wrapDoc);
1066         } catch (DocumentException de) {
1067             throw de;
1068         } catch (Exception e) {
1069             if (logger.isDebugEnabled()) {
1070                 logger.debug("Caught exception ", e);
1071             }
1072             throw new NuxeoDocumentException(e);
1073         } finally {
1074             if (repoSession != null) {
1075                 releaseRepositorySession(ctx, repoSession);
1076             }
1077         }
1078     }
1079
1080     /**
1081      * getAll get all documents for an entity entity service from the Nuxeo
1082      * repository
1083      *
1084      * @param ctx service context under which this method is invoked
1085      * @param handler should be used by the caller to provide and transform the
1086      * document
1087      * @throws DocumentNotFoundException
1088      * @throws TransactionException
1089      * @throws DocumentException
1090      */
1091     @Override
1092     public void getAll(ServiceContext ctx, DocumentHandler handler)
1093             throws DocumentNotFoundException, TransactionException, DocumentException {
1094         if (handler == null) {
1095             throw new IllegalArgumentException(
1096                     "RepositoryJavaClient.getAll: handler is missing");
1097         }
1098         String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
1099         if (nuxeoWspaceId == null) {
1100             throw new DocumentNotFoundException(
1101                     "Unable to find workspace for service "
1102                     + ctx.getServiceName()
1103                     + " check if the workspace exists in the Nuxeo repository.");
1104         }
1105
1106         CoreSessionInterface repoSession = null;
1107         try {
1108             handler.prepare(Action.GET_ALL);
1109             repoSession = getRepositorySession(ctx);
1110             DocumentRef wsDocRef = new IdRef(nuxeoWspaceId);
1111             DocumentModelList docList = repoSession.getChildren(wsDocRef);
1112             //set reposession to handle the document
1113             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1114             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
1115             handler.handle(Action.GET_ALL, wrapDoc);
1116             handler.complete(Action.GET_ALL, wrapDoc);
1117         } catch (DocumentException de) {
1118             throw de;
1119         } catch (Exception e) {
1120             if (logger.isDebugEnabled()) {
1121                 logger.debug("Caught exception ", e);
1122             }
1123             throw new NuxeoDocumentException(e);
1124         } finally {
1125             if (repoSession != null) {
1126                 releaseRepositorySession(ctx, repoSession);
1127             }
1128         }
1129     }
1130
1131     private boolean isClauseEmpty(String theString) {
1132         boolean result = true;
1133         if (theString != null && !theString.isEmpty()) {
1134             result = false;
1135         }
1136         return result;
1137     }
1138
1139     public DocumentWrapper<DocumentModel> getDocFromCsid(
1140             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1141             CoreSessionInterface repoSession,
1142             String csid)
1143             throws Exception {
1144         DocumentWrapper<DocumentModel> result = null;
1145
1146         result = new DocumentWrapperImpl<DocumentModel>(NuxeoUtils.getDocFromCsid(ctx, repoSession, csid));
1147
1148         return result;
1149     }
1150
1151     /*
1152      * A method to find a CollectionSpace document (of any type) given just a service context and
1153      * its CSID.  A search across *all* service workspaces (within a given tenant context) is performed to find
1154      * the document
1155      *
1156      * This query searches Nuxeo's Hierarchy table where our CSIDs are stored in the "name" column.
1157      */
1158     @Override
1159     public DocumentWrapper<DocumentModel> getDocFromCsid(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1160             String csid)
1161             throws Exception {
1162         DocumentWrapper<DocumentModel> result = null;
1163         CoreSessionInterface repoSession = null;
1164         try {
1165             repoSession = getRepositorySession(ctx);
1166             result = getDocFromCsid(ctx, repoSession, csid);
1167         } finally {
1168             if (repoSession != null) {
1169                 releaseRepositorySession(ctx, repoSession);
1170             }
1171         }
1172
1173         if (logger.isWarnEnabled() == true) {
1174             logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
1175         }
1176
1177         return result;
1178     }
1179
1180     /**
1181      * Returns a URI value for a document in the Nuxeo repository
1182      *
1183      * @param wrappedDoc a wrapped documentModel
1184      * @throws ClientException
1185      * @return a document URI
1186      */
1187     @Override
1188     public String getDocURI(DocumentWrapper<DocumentModel> wrappedDoc) throws ClientException {
1189         DocumentModel docModel = wrappedDoc.getWrappedObject();
1190         String uri = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1191                 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
1192         return uri;
1193     }
1194
1195     /*
1196      * See CSPACE-5036 - How to make CMISQL queries from Nuxeo
1197      */
1198     private IterableQueryResult makeCMISQLQuery(CoreSessionInterface repoSession, String query, QueryContext queryContext) throws DocumentException {
1199         IterableQueryResult result = null;
1200         /** Threshold over which temporary files are not kept in memory. */
1201         final int THRESHOLD = 1024 * 1024;
1202
1203         try {
1204             logger.debug(String.format("Performing a CMIS query on Nuxeo repository named %s",
1205                         repoSession.getRepositoryName()));
1206
1207             ThresholdOutputStreamFactory streamFactory = ThresholdOutputStreamFactory.newInstance(
1208                     null, THRESHOLD, -1, false);
1209             CallContextImpl callContext = new CallContextImpl(
1210                     CallContext.BINDING_LOCAL,
1211                     CmisVersion.CMIS_1_1,
1212                     repoSession.getRepositoryName(),
1213                     null, // ServletContext
1214                     null, // HttpServletRequest
1215                     null, // HttpServletResponse
1216                     new NuxeoCmisServiceFactory(),
1217                     streamFactory);
1218             callContext.put(CallContext.USERNAME, repoSession.getPrincipal().getName());
1219
1220             NuxeoCmisService cmisService = new NuxeoCmisService(repoSession.getCoreSession());
1221             result = repoSession.queryAndFetch(query, "CMISQL", cmisService);
1222         } catch (ClientException e) {
1223             // TODO Auto-generated catch block
1224             logger.error("Encounter trouble making the following CMIS query: " + query, e);
1225             throw new NuxeoDocumentException(e);
1226         }
1227
1228         return result;
1229     }
1230
1231     /**
1232      * getFiltered get all documents for an entity service from the Document
1233      * repository, given filter parameters specified by the handler.
1234      *
1235      * @param ctx service context under which this method is invoked
1236      * @param handler should be used by the caller to provide and transform the
1237      * document
1238      * @throws DocumentNotFoundException if workspace not found
1239      * @throws TransactionException
1240      * @throws DocumentException
1241      */
1242     @Override
1243     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
1244             throws DocumentNotFoundException, TransactionException, DocumentException {
1245
1246         DocumentFilter filter = handler.getDocumentFilter();
1247         String oldOrderBy = filter.getOrderByClause();
1248         if (isClauseEmpty(oldOrderBy) == true) {
1249             filter.setOrderByClause(DocumentFilter.ORDER_BY_LAST_UPDATED);
1250         }
1251         QueryContext queryContext = new QueryContext(ctx, handler);
1252
1253         CoreSessionInterface repoSession = null;
1254         try {
1255             handler.prepare(Action.GET_ALL);
1256             repoSession = getRepositorySession(ctx); //Keeps a refcount here for the repository session so you need to release this when finished
1257
1258             DocumentModelList docList = null;
1259             // JDBC query
1260             if (handler.isJDBCQuery() == true) {
1261                 docList = getFilteredJDBC(repoSession, ctx, handler);
1262             // CMIS query
1263             } else if (handler.isCMISQuery() == true) { //FIXME: REM - Need to deal with paging info in CMIS query
1264                 if (isSubjectOrObjectQuery(ctx)) {
1265                         docList = getFilteredCMISForSubjectOrObject(repoSession, ctx, handler, queryContext);
1266                 } else {
1267                     docList = getFilteredCMIS(repoSession, ctx, handler, queryContext);
1268                 }
1269             // NXQL query
1270             } else {
1271                 String query = NuxeoUtils.buildNXQLQuery(queryContext);
1272                 if (logger.isDebugEnabled()) {
1273                     logger.debug("Executing NXQL query: " + query.toString());
1274                 }
1275                 Profiler profiler = new Profiler(this, 2);
1276                 profiler.log("Executing NXQL query: " + query.toString());
1277                 profiler.start();
1278                 // If we have a page size and/or offset, then reflect those values
1279                 // when constructing the query, and also pass 'true' to get totalSize
1280                 // in the returned DocumentModelList.
1281                 if ((queryContext.getDocFilter().getOffset() > 0) || (queryContext.getDocFilter().getPageSize() > 0)) {
1282                     docList = repoSession.query(query, null,
1283                             queryContext.getDocFilter().getPageSize(), queryContext.getDocFilter().getOffset(), true);
1284                 } else {
1285                     docList = repoSession.query(query);
1286                 }
1287                 profiler.stop();
1288             }
1289
1290             //set repoSession to handle the document
1291             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1292             DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
1293             handler.handle(Action.GET_ALL, wrapDoc);
1294             handler.complete(Action.GET_ALL, wrapDoc);
1295         } catch (DocumentException de) {
1296             throw de;
1297         } catch (Exception e) {
1298             if (logger.isDebugEnabled()) {
1299                 logger.debug("Caught exception ", e); // REM - 1/17/2014: Check for org.nuxeo.ecm.core.api.ClientException and re-attempt
1300             }
1301             throw new NuxeoDocumentException(e);
1302         } finally {
1303             if (repoSession != null) {
1304                 releaseRepositorySession(ctx, repoSession);
1305             }
1306         }
1307     }
1308
1309     /**
1310      * Perform a database query, via JDBC and SQL, to retrieve matching records
1311      * based on filter criteria.
1312      *
1313      * Although this method currently has a general-purpose name, it is
1314      * currently dedicated to a specific task: that of improving performance
1315      * for partial term matching queries on authority items / terms, via
1316      * the use of a hand-tuned SQL query, rather than via the generated SQL
1317      * produced by Nuxeo from an NXQL query.  (See CSPACE-6361 for a task
1318      * to generalize this method.)
1319      *
1320      * @param repoSession a repository session.
1321      * @param ctx the service context.
1322      * @param handler a relevant document handler.
1323      * @return a list of document models matching the search criteria.
1324      * @throws Exception
1325      */
1326     private DocumentModelList getFilteredJDBC(CoreSessionInterface repoSession, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1327             DocumentHandler handler) throws Exception {
1328         DocumentModelList result = new DocumentModelListImpl();
1329
1330         // FIXME: Get all of the following values from appropriate external constants.
1331         //
1332         // At present, the two constants below are duplicated in both RepositoryClientImpl
1333         // and in AuthorityItemDocumentModelHandler.
1334         final String TERM_GROUP_LIST_NAME = "TERM_GROUP_LIST_NAME";
1335         final String TERM_GROUP_TABLE_NAME_PARAM = "TERM_GROUP_TABLE_NAME";
1336         final String IN_AUTHORITY_PARAM = "IN_AUTHORITY";
1337         // Get this from a constant in AuthorityResource or equivalent
1338         final String PARENT_WILDCARD = "_ALL_";
1339
1340         // Build two SQL statements, to be executed within a single transaction:
1341         // the first statement to control join order, and the second statement
1342         // representing the actual 'get filtered' query
1343
1344         // Build the join control statement
1345         //
1346         // Per http://www.postgresql.org/docs/9.2/static/runtime-config-query.html#GUC-JOIN-COLLAPSE-LIMIT
1347         // "Setting [this value] to 1 prevents any reordering of explicit JOINs.
1348         // Thus, the explicit join order specified in the query will be the
1349         // actual order in which the relations are joined."
1350         // See CSPACE-5945 for further discussion of why this setting is needed.
1351         //
1352         // Adding this statement is commented out here for now.  It significantly
1353         // improved query performance for authority item / term queries where
1354         // large numbers of rows were retrieved, but appears to have resulted
1355         // in consistently slower-than-desired query performance where zero or
1356         // very few records were retrieved. See notes on CSPACE-5945. - ADR 2013-04-09
1357         // String joinControlSql = "SET LOCAL join_collapse_limit TO 1;";
1358
1359         // Build the query statement
1360         //
1361         // Start with the default query
1362         String selectStatement =
1363                 "SELECT DISTINCT commonschema.id"
1364                 + " FROM " + handler.getServiceContext().getCommonPartLabel() + " commonschema";
1365
1366         String joinClauses =
1367                 " INNER JOIN misc"
1368                 + "  ON misc.id = commonschema.id"
1369                 + " INNER JOIN hierarchy hierarchy_termgroup"
1370                 + "  ON hierarchy_termgroup.parentid = misc.id"
1371                 + " INNER JOIN "  + handler.getJDBCQueryParams().get(TERM_GROUP_TABLE_NAME_PARAM) + " termgroup"
1372                 + "  ON termgroup.id = hierarchy_termgroup.id ";
1373
1374         String whereClause;
1375         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1376         // Value for replaceable parameter 1 in the query
1377         String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
1378         // If the value of the partial term query parameter is blank ('pt='),
1379         // return all records, subject to restriction by any limit clause
1380         if (Tools.isBlank(partialTerm)) {
1381            whereClause = "";
1382         } else {
1383            // Otherwise, return records that match the supplied partial term
1384            whereClause =
1385                 " WHERE (termgroup.termdisplayname ILIKE ?)";
1386         }
1387
1388         // At present, results are ordered in code, below, rather than in SQL,
1389         // and the orderByClause below is thus intentionally blank.
1390         //
1391         // To implement the orderByClause below in SQL; e.g. via
1392         // 'ORDER BY termgroup.termdisplayname', the relevant column
1393         // must be returned by the SELECT statement.
1394         String orderByClause = "";
1395
1396         String limitClause;
1397         TenantBindingConfigReaderImpl tReader =
1398                 ServiceMain.getInstance().getTenantBindingConfigReader();
1399         TenantBindingType tenantBinding = tReader.getTenantBinding(ctx.getTenantId());
1400         String maxListItemsLimit = TenantBindingUtils.getPropertyValue(tenantBinding,
1401                 IQueryManager.MAX_LIST_ITEMS_RETURNED_LIMIT_ON_JDBC_QUERIES);
1402         limitClause =
1403                 " LIMIT " + getMaxItemsLimitOnJdbcQueries(maxListItemsLimit); // implicit int-to-String conversion
1404
1405         // After building the individual parts of the query, set the values
1406         // of replaceable parameters that will be inserted into that query
1407         // and optionally add restrictions
1408
1409         List<String> params = new ArrayList<>();
1410
1411         if (Tools.notBlank(whereClause)) {
1412
1413             // Read tenant bindings configuration to determine whether
1414             // to automatically insert leading, as well as trailing, wildcards
1415             // into the term matching string.
1416             String usesStartingWildcard = TenantBindingUtils.getPropertyValue(tenantBinding,
1417                     IQueryManager.TENANT_USES_STARTING_WILDCARD_FOR_PARTIAL_TERM);
1418             // Handle user-provided leading wildcard characters, in the
1419             // configuration where a leading wildcard is not automatically inserted.
1420             // (The user-provided wildcard must be in the first, or "starting"
1421             // character position in the partial term value.)
1422             if (Tools.notBlank(usesStartingWildcard)) {
1423                 if (usesStartingWildcard.equalsIgnoreCase(Boolean.FALSE.toString())) {
1424                     partialTerm = handleProvidedStartingWildcard(partialTerm);
1425                     // Otherwise, in the configuration where a leading wildcard
1426                     // is usually automatically inserted, handle the cases where
1427                     // a user has entered an anchor character in the first position
1428                     // in the starting term value. In those cases, strip that
1429                     // anchor character and don't add a leading wildcard
1430                 } else {
1431                     if (partialTerm.startsWith(USER_SUPPLIED_ANCHOR_CHAR)) {
1432                         partialTerm = partialTerm.substring(1, partialTerm.length());
1433                         // Otherwise, automatically add a leading wildcard
1434                     } else {
1435                         partialTerm = JDBCTools.SQL_WILDCARD + partialTerm;
1436                     }
1437                 }
1438             }
1439             // Add SQL wildcards in the midst of the partial term match search
1440             // expression, whever user-supplied wildcards appear, except in the
1441             // first or last character positions of the search expression.
1442             partialTerm = subtituteWildcardsInPartialTerm(partialTerm);
1443
1444             // If a designated 'anchor character' is present as the last character
1445             // in the search expression, strip that character and don't add
1446             // a trailing wildcard
1447             int lastCharPos = partialTerm.length() - 1;
1448             if (partialTerm.endsWith(USER_SUPPLIED_ANCHOR_CHAR) && lastCharPos > 0) {
1449                     partialTerm = partialTerm.substring(0, lastCharPos);
1450             } else {
1451                 // Otherwise, automatically add a trailing wildcard
1452                 partialTerm = partialTerm + JDBCTools.SQL_WILDCARD;
1453             }
1454             params.add(partialTerm);
1455         }
1456
1457         // Optionally add restrictions to the default query, based on variables
1458         // in the current request
1459
1460         // Restrict the query to filter out deleted records, if requested
1461         String includeDeleted = queryParams.getFirst(WorkflowClient.WORKFLOW_QUERY_DELETED_QP);
1462         if (includeDeleted != null && includeDeleted.equalsIgnoreCase(Boolean.FALSE.toString())) {
1463             whereClause = whereClause
1464                     + "  AND (misc.lifecyclestate <> '" + WorkflowClient.WORKFLOWSTATE_DELETED + "')"
1465                     + "  AND (misc.lifecyclestate <> '" + WorkflowClient.WORKFLOWSTATE_LOCKED_DELETED + "')"
1466                     + "  AND (misc.lifecyclestate <> '" + WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED + "')";
1467         }
1468
1469         // If a particular authority is specified, restrict the query further
1470         // to return only records within that authority
1471         String inAuthorityValue = (String) handler.getJDBCQueryParams().get(IN_AUTHORITY_PARAM);
1472         if (Tools.notBlank(inAuthorityValue)) {
1473             // Handle the '_ALL_' case for inAuthority
1474             if (inAuthorityValue.equals(PARENT_WILDCARD)) {
1475                 // Add nothing to the query here if it should match within all authorities
1476             } else {
1477                 whereClause = whereClause
1478                     + "  AND (commonschema.inauthority = ?)";
1479                 params.add(inAuthorityValue); // Value for replaceable parameter 2 in the query
1480             }
1481         }
1482
1483         // Restrict the query further to return only records pertaining to
1484         // the current tenant, unless:
1485         // * Data for this service, in this tenant, is stored in its own,
1486         //   separate repository, rather than being intermingled with other
1487         //   tenants' data in the default repository; or
1488         // * Restriction by tenant ID in JDBC queries has been disabled,
1489         //   via configuration for this tenant,
1490         if (restrictJDBCQueryByTenantID(tenantBinding, ctx)) {
1491                 joinClauses = joinClauses
1492                     + " INNER JOIN collectionspace_core core"
1493                     + "  ON core.id = hierarchy_termgroup.parentid";
1494                 whereClause = whereClause
1495                     + "  AND (core.tenantid = ?)";
1496                 params.add(ctx.getTenantId()); // Value for replaceable parameter 3 in the query
1497         }
1498
1499         // Piece together the SQL query from its parts
1500         String querySql = selectStatement + joinClauses + whereClause + orderByClause + limitClause;
1501
1502         // Note: PostgreSQL 9.2 introduced a change that may improve performance
1503         // of certain queries using JDBC PreparedStatements.  See comments on
1504         // CSPACE-5943 for details.
1505         //
1506         // See a comment above for the reason that the joinControl SQL statement,
1507         // along with its corresponding prepared statement builder, is commented out for now.
1508         // PreparedStatementBuilder joinControlBuilder = new PreparedStatementBuilder(joinControlSql);
1509         PreparedStatementSimpleBuilder queryBuilder = new PreparedStatementSimpleBuilder(querySql, params);
1510         List<PreparedStatementBuilder> builders = new ArrayList<>();
1511         // builders.add(joinControlBuilder);
1512         builders.add(queryBuilder);
1513         String dataSourceName = JDBCTools.NUXEO_DATASOURCE_NAME;
1514         String repositoryName = ctx.getRepositoryName();
1515         final Boolean EXECUTE_WITHIN_TRANSACTION = true;
1516         Set<String> docIds = new HashSet<>();
1517         try {
1518                 String cspaceInstanceId = ServiceMain.getInstance().getCspaceInstanceId();
1519             List<CachedRowSet> resultsList = JDBCTools.executePreparedQueries(builders,
1520                 dataSourceName, repositoryName, cspaceInstanceId, EXECUTE_WITHIN_TRANSACTION);
1521
1522             // At least one set of results is expected, from the second prepared
1523             // statement to be executed.
1524             // If fewer results are returned, return an empty list of document models
1525             if (resultsList == null || resultsList.size() < 1) {
1526                 return result; // return an empty list of document models
1527             }
1528             // The join control query (if enabled - it is currently commented
1529             // out as per comments above) will not return results, so query results
1530             // will be the first set of results (rowSet) returned in the list
1531             CachedRowSet queryResults = resultsList.get(0);
1532
1533             // If the result from executing the query is null or contains zero rows,
1534             // return an empty list of document models
1535             if (queryResults == null) {
1536                 return result; // return an empty list of document models
1537             }
1538             queryResults.last();
1539             if (queryResults.getRow() == 0) {
1540                 return result; // return an empty list of document models
1541             }
1542
1543             // Otherwise, get the document IDs from the results of the query
1544             String id;
1545             queryResults.beforeFirst();
1546             while (queryResults.next()) {
1547                 id = queryResults.getString(1);
1548                 if (Tools.notBlank(id)) {
1549                     docIds.add(id);
1550                 }
1551             }
1552         } catch (SQLException sqle) {
1553             logger.warn("Could not obtain document IDs via SQL query '" + querySql + "': " + sqle.getMessage());
1554             return result; // return an empty list of document models
1555         }
1556
1557         // Get a list of document models, using the list of IDs obtained from the query
1558         //
1559         // FIXME: Check whether we have a 'get document models from list of CSIDs'
1560         // utility method like this, and if not, add this to the appropriate
1561         // framework class
1562         DocumentModel docModel;
1563         for (String docId : docIds) {
1564             docModel = NuxeoUtils.getDocumentModel(repoSession, docId);
1565             if (docModel == null) {
1566                 logger.warn("Could not obtain document model for document with ID " + docId);
1567             } else {
1568                 result.add(docModel);
1569             }
1570         }
1571
1572         // Order the results
1573         final String COMMON_PART_SCHEMA = handler.getServiceContext().getCommonPartLabel();
1574         final String DISPLAY_NAME_XPATH =
1575                 "//" + handler.getJDBCQueryParams().get(TERM_GROUP_LIST_NAME) + "/[0]/termDisplayName";
1576         Collections.sort(result, new Comparator<DocumentModel>() {
1577             @Override
1578             public int compare(DocumentModel doc1, DocumentModel doc2) {
1579                 String termDisplayName1 = null;
1580                 String termDisplayName2 = null;
1581                 try {
1582                         termDisplayName1 = (String) NuxeoUtils.getXPathValue(doc1, COMMON_PART_SCHEMA, DISPLAY_NAME_XPATH);
1583                         termDisplayName2 = (String) NuxeoUtils.getXPathValue(doc2, COMMON_PART_SCHEMA, DISPLAY_NAME_XPATH);
1584                 } catch (NuxeoDocumentException e) {
1585                         throw new RuntimeException(e);  // We need to throw a RuntimeException because the compare() method of the Comparator interface does not support throwing an Exception
1586                 }
1587                 return termDisplayName1.compareToIgnoreCase(termDisplayName2);
1588             }
1589         });
1590
1591         return result;
1592     }
1593
1594
1595     private DocumentModelList getFilteredCMIS(CoreSessionInterface repoSession,
1596                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, DocumentHandler handler, QueryContext queryContext)
1597             throws DocumentNotFoundException, DocumentException {
1598
1599         DocumentModelList result = new DocumentModelListImpl();
1600         try {
1601             String query = handler.getCMISQuery(queryContext);
1602
1603             DocumentFilter docFilter = handler.getDocumentFilter();
1604             int pageSize = docFilter.getPageSize();
1605             int offset = docFilter.getOffset();
1606             if (logger.isDebugEnabled()) {
1607                 logger.debug("Executing CMIS query: " + query.toString()
1608                         + "with pageSize: " + pageSize + " at offset: " + offset);
1609             }
1610
1611             // If we have limit and/or offset, then pass true to get totalSize
1612             // in returned DocumentModelList.
1613             Profiler profiler = new Profiler(this, 2);
1614             profiler.log("Executing CMIS query: " + query.toString());
1615             profiler.start();
1616             //
1617             IterableQueryResult queryResult = makeCMISQLQuery(repoSession, query, queryContext);
1618             try {
1619                 int totalSize = (int) queryResult.size();
1620                 ((DocumentModelListImpl) result).setTotalSize(totalSize);
1621                 // Skip the rows before our offset
1622                 if (offset > 0) {
1623                     queryResult.skipTo(offset);
1624                 }
1625                 int nRows = 0;
1626                 for (Map<String, Serializable> row : queryResult) {
1627                     if (logger.isTraceEnabled()) {
1628                         logger.trace(" Hierarchy Table ID is:" + row.get(IQueryManager.CMIS_TARGET_NUXEO_ID)
1629                                 + " nuxeo:pathSegment is: " + row.get(IQueryManager.CMIS_TARGET_NAME));
1630                     }
1631                     String nuxeoId = (String) row.get(IQueryManager.CMIS_TARGET_NUXEO_ID);
1632                     DocumentModel docModel = NuxeoUtils.getDocumentModel(repoSession, nuxeoId);
1633                     result.add(docModel);
1634                     nRows++;
1635                     if (nRows >= pageSize && pageSize != 0) { // A page size of zero means that they want all of them
1636                         logger.debug("Got page full of items - quitting");
1637                         break;
1638                     }
1639                 }
1640             } finally {
1641                 queryResult.close();
1642             }
1643             //
1644             profiler.stop();
1645
1646         } catch (Exception e) {
1647             if (logger.isDebugEnabled()) {
1648                 logger.debug("Caught exception ", e);
1649             }
1650             throw new NuxeoDocumentException(e);
1651         }
1652
1653         //
1654         // Since we're not supporting paging yet for CMIS queries, we need to perform
1655         // a workaround for the paging information we return in our list of results
1656         //
1657         /*
1658          if (result != null) {
1659          docFilter.setStartPage(0);
1660          if (totalSize > docFilter.getPageSize()) {
1661          docFilter.setPageSize(totalSize);
1662          ((DocumentModelListImpl)result).setTotalSize(totalSize);
1663          }
1664          }
1665          */
1666
1667         return result;
1668     }
1669
1670     private String logException(Exception e, String msg) {
1671         String result = null;
1672
1673         String exceptionMessage = e.getMessage();
1674         exceptionMessage = exceptionMessage != null ? exceptionMessage : "<No details provided>";
1675         result = msg = msg + ". Caught exception:" + exceptionMessage;
1676
1677         if (logger.isTraceEnabled() == true) {
1678             logger.error(msg, e);
1679         } else {
1680             logger.error(msg);
1681         }
1682
1683         return result;
1684     }
1685
1686     /**
1687      * update given document in the Nuxeo repository
1688      *
1689      * @param ctx service context under which this method is invoked
1690      * @param csid of the document
1691      * @param handler should be used by the caller to provide and transform the
1692      * document
1693      * @throws BadRequestException
1694      * @throws DocumentNotFoundException
1695      * @throws TransactionException if the transaction times out or otherwise
1696      * cannot be successfully completed
1697      * @throws DocumentException
1698      */
1699         @Override
1700     public void update(ServiceContext ctx, String csid, DocumentHandler handler)
1701             throws BadRequestException, DocumentNotFoundException, TransactionException,
1702             DocumentException {
1703         if (handler == null) {
1704             throw new IllegalArgumentException(
1705                     "RepositoryJavaClient.update: document handler is missing.");
1706         }
1707
1708         CoreSessionInterface repoSession = null;
1709         try {
1710             handler.prepare(Action.UPDATE);
1711             repoSession = getRepositorySession(ctx);
1712             DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
1713             DocumentModel doc = null;
1714             try {
1715                 doc = repoSession.getDocument(docRef);
1716             } catch (org.nuxeo.ecm.core.api.DocumentNotFoundException ce) {
1717                 String msg = logException(ce,
1718                                 String.format("Could not find %s resource/record to update with CSID=%s", ctx.getDocumentType(), csid));
1719                 throw new DocumentNotFoundException(msg, ce);
1720             }
1721             // Check for a versioned document, and check In and Out before we proceed.
1722             if (((DocumentModelHandler) handler).supportsVersioning()) {
1723                 /* Once we advance to 5.5 or later, we can add this.
1724                  * See also https://jira.nuxeo.com/browse/NXP-8506
1725                  if(!doc.isVersionable()) {
1726                  throw new NuxeoDocumentException("Configuration for: "
1727                  +handler.getServiceContextPath()+" supports versioning, but Nuxeo config does not!");
1728                  }
1729                  */
1730                 /* Force a version number - Not working. Apparently we need to configure the uid schema??
1731                  if(doc.getProperty("uid","major_version") == null) {
1732                  doc.setProperty("uid","major_version",1);
1733                  }
1734                  if(doc.getProperty("uid","minor_version") == null) {
1735                  doc.setProperty("uid","minor_version",0);
1736                  }
1737                  */
1738                 doc.checkIn(VersioningOption.MINOR, null);
1739                 doc.checkOut();
1740             }
1741
1742             //
1743             // Set reposession to handle the document
1744             //
1745             ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1746             DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
1747             handler.handle(Action.UPDATE, wrapDoc);
1748             repoSession.saveDocument(doc);
1749             repoSession.save();
1750             // Refresh the doc after save, in case a documentModified event handler has modified
1751             // the document post-save. We want those changes to be reflected in the returned document.
1752             doc.refresh();
1753             handler.complete(Action.UPDATE, wrapDoc);
1754         } catch (BadRequestException bre) {
1755             if (ctx.isRollbackOnException()) {
1756                 rollbackTransaction(repoSession);
1757             }
1758             throw bre;
1759         } catch (DocumentException de) {
1760             if (ctx.isRollbackOnException()) {
1761                 rollbackTransaction(repoSession);
1762             }
1763
1764             throw de;
1765         } catch (CSWebApplicationException wae) {
1766             if (ctx.isRollbackOnException()) {
1767                 rollbackTransaction(repoSession);
1768             }
1769             throw wae;
1770         } catch (Throwable e) {
1771             if (ctx.isRollbackOnException()) {
1772                 rollbackTransaction(repoSession);
1773             }
1774             throw new NuxeoDocumentException(e);
1775         } finally {
1776             if (repoSession != null) {
1777                 releaseRepositorySession(ctx, repoSession);
1778             }
1779         }
1780     }
1781
1782     /**
1783      * Save a documentModel to the Nuxeo repository.
1784      *
1785      * @param ctx service context under which this method is invoked
1786      * @param repoSession
1787      * @param docModel the document to save
1788      * @param fSaveSession if TRUE, will call CoreSessionInterface.save() to save
1789      * accumulated changes.
1790      * @throws ClientException
1791      * @throws DocumentException
1792      */
1793         @Deprecated
1794     public void saveDocWithoutHandlerProcessing(
1795             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1796             CoreSessionInterface repoSession,
1797             DocumentModel docModel,
1798             boolean fSaveSession)
1799             throws ClientException, DocumentException {
1800
1801         try {
1802             repoSession.saveDocument(docModel);
1803             if (fSaveSession) {
1804                 repoSession.save();
1805             }
1806         } catch (ClientException ce) {
1807             throw ce;
1808         } catch (Exception e) {
1809             if (logger.isDebugEnabled()) {
1810                 logger.debug("Caught exception ", e);
1811             }
1812             throw new NuxeoDocumentException(e);
1813         }
1814     }
1815
1816     /**
1817      * Save a list of documentModels to the Nuxeo repository.
1818      *
1819      * @param ctx service context under which this method is invoked
1820      * @param repoSession a repository session
1821      * @param docModelList a list of document models
1822      * @param fSaveSession if TRUE, will call CoreSessionInterface.save() to save
1823      * accumulated changes.
1824      * @throws ClientException
1825      * @throws DocumentException
1826      */
1827     public void saveDocListWithoutHandlerProcessing(
1828             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1829             CoreSessionInterface repoSession,
1830             DocumentModelList docList,
1831             boolean fSaveSession)
1832             throws ClientException, DocumentException {
1833         try {
1834             DocumentModel[] docModelArray = new DocumentModel[docList.size()];
1835             repoSession.saveDocuments(docList.toArray(docModelArray));
1836             if (fSaveSession) {
1837                 repoSession.save();
1838             }
1839         } catch (ClientException ce) {
1840             throw ce;
1841         } catch (Exception e) {
1842             logger.error("Caught exception ", e);
1843             throw new NuxeoDocumentException(e);
1844         }
1845     }
1846
1847     @Override
1848         public void deleteWithWhereClause(@SuppressWarnings("rawtypes") ServiceContext ctx, String whereClause,
1849                         @SuppressWarnings("rawtypes") DocumentHandler handler) throws
1850                         DocumentNotFoundException, DocumentException {
1851         if (ctx == null) {
1852             throw new IllegalArgumentException(
1853                     "delete(ctx, specifier): ctx is missing");
1854         }
1855         if (logger.isDebugEnabled()) {
1856             logger.debug("Deleting document with whereClause=" + whereClause);
1857         }
1858
1859         DocumentWrapper<DocumentModel> foundDocWrapper = this.findDoc(ctx, whereClause);
1860         if (foundDocWrapper != null) {
1861                 DocumentModel docModel = foundDocWrapper.getWrappedObject();
1862                 String csid = docModel.getName();
1863                 this.delete(ctx, csid, handler);
1864         }
1865     }
1866
1867     /**
1868      * delete a document from the Nuxeo repository
1869      *
1870      * @param ctx service context under which this method is invoked
1871      * @param id of the document
1872      * @throws DocumentException
1873      */
1874     @Override
1875     public boolean delete(ServiceContext ctx, List<String> idList, DocumentHandler handler) throws DocumentNotFoundException,
1876             DocumentException, TransactionException {
1877         boolean result = true;
1878
1879         if (ctx == null) {
1880             throw new IllegalArgumentException(
1881                     "delete(ctx, ix, handler): ctx is missing");
1882         }
1883         if (handler == null) {
1884             throw new IllegalArgumentException(
1885                     "delete(ctx, ix, handler): handler is missing");
1886         }
1887
1888         CoreSessionInterface repoSession = null;
1889         try {
1890             handler.prepare(Action.DELETE);
1891             repoSession = getRepositorySession(ctx);
1892
1893             for (String id : idList) {
1894                 if (logger.isDebugEnabled()) {
1895                     logger.debug("Deleting document with CSID=" + id);
1896                 }
1897                     DocumentWrapper<DocumentModel> wrapDoc = null;
1898                     try {
1899                         DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
1900                         wrapDoc = new DocumentWrapperImpl<DocumentModel>(repoSession.getDocument(docRef));
1901                         ((DocumentModelHandler) handler).setRepositorySession(repoSession);
1902                         if (handler.handle(Action.DELETE, wrapDoc) == true) {
1903                                 repoSession.removeDocument(docRef);
1904                                 if (logger.isDebugEnabled()) {
1905                                         String msg = String.format("DELETE - User '%s' hard-deleted document CSID=%s of type %s.",
1906                                                         ctx.getUserId(), id, ctx.getDocumentType());
1907                                         logger.debug(msg);
1908                                 }
1909                         } else {
1910                                 String msg = String.format("Could not delete %s resource with csid=%s.",
1911                                                 handler.getServiceContext().getServiceName(), id);
1912                                 throw new DocumentException(msg);
1913                         }
1914                     } catch (org.nuxeo.ecm.core.api.DocumentNotFoundException ce) {
1915                         String msg = logException(ce,
1916                                         String.format("Could not find %s resource/record to delete with CSID=%s", ctx.getDocumentType(), id));
1917                         throw new DocumentNotFoundException(msg, ce);
1918                     }
1919                     repoSession.save();
1920                     handler.complete(Action.DELETE, wrapDoc);
1921             }
1922         } catch (DocumentException de) {
1923             if (ctx.isRollbackOnException()) {
1924                     rollbackTransaction(repoSession);
1925             }
1926             throw de;
1927         } catch (Throwable e) {
1928             if (ctx.isRollbackOnException()) {
1929                 rollbackTransaction(repoSession);
1930             }
1931             throw new NuxeoDocumentException(e);
1932         } finally {
1933             if (repoSession != null) {
1934                 releaseRepositorySession(ctx, repoSession);
1935             }
1936         }
1937
1938         return result;
1939     }
1940
1941     /**
1942      * delete a document from the Nuxeo repository
1943      *
1944      * @param ctx service context under which this method is invoked
1945      * @param id of the document
1946      * @throws DocumentException
1947      */
1948     @Override
1949     public boolean delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException,
1950             DocumentException, TransactionException {
1951         boolean result;
1952
1953         List<String> idList = new ArrayList<String>();
1954         idList.add(id);
1955         result = delete(ctx, idList, handler);
1956
1957         return result;
1958     }
1959
1960     /* (non-Javadoc)
1961      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
1962      */
1963     @Override
1964     @Deprecated
1965     public void delete(@SuppressWarnings("rawtypes") ServiceContext ctx, String id)
1966             throws DocumentNotFoundException, DocumentException {
1967         throw new UnsupportedOperationException();
1968         // Use the other delete instead
1969     }
1970
1971     @Override
1972     public Hashtable<String, String> retrieveWorkspaceIds(RepositoryDomainType repoDomain) throws Exception {
1973         return NuxeoConnectorEmbedded.getInstance().retrieveWorkspaceIds(repoDomain);
1974     }
1975
1976     @Override
1977     public String createDomain(RepositoryDomainType repositoryDomain) throws Exception {
1978         CoreSessionInterface repoSession = null;
1979         String domainId = null;
1980         try {
1981             //
1982             // Open a connection to the domain's repo/db
1983             //
1984             String repoName = repositoryDomain.getRepositoryName();
1985             repoSession = getRepositorySession(repoName); // domainName=storageName=repoName=databaseName
1986             //
1987             // First create the top-level domain directory
1988             //
1989             String domainName = repositoryDomain.getStorageName();
1990             DocumentRef parentDocRef = new PathRef("/");
1991             DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
1992             DocumentModel domainDoc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
1993                     domainName, NUXEO_CORE_TYPE_DOMAIN);
1994             domainDoc.setPropertyValue("dc:title", domainName);
1995             domainDoc.setPropertyValue("dc:description", "A CollectionSpace domain "
1996                     + domainName);
1997             domainDoc = repoSession.createDocument(domainDoc);
1998             domainId = domainDoc.getId();
1999             repoSession.save();
2000             //
2001             // Next, create a "Workspaces" root directory to contain the workspace folders for the individual service documents
2002             //
2003             DocumentModel workspacesRoot = repoSession.createDocumentModel(domainDoc.getPathAsString(),
2004                     NuxeoUtils.Workspaces, NUXEO_CORE_TYPE_WORKSPACEROOT);
2005             workspacesRoot.setPropertyValue("dc:title", NuxeoUtils.Workspaces);
2006             workspacesRoot.setPropertyValue("dc:description", "A CollectionSpace workspaces directory for "
2007                     + domainDoc.getPathAsString());
2008             workspacesRoot = repoSession.createDocument(workspacesRoot);
2009             String workspacesRootId = workspacesRoot.getId();
2010             repoSession.save();
2011
2012             if (logger.isDebugEnabled()) {
2013                 logger.debug("Created tenant domain name=" + domainName
2014                         + " id=" + domainId + " "
2015                         + NuxeoUtils.Workspaces + " id=" + workspacesRootId);
2016                 logger.debug("Path to Domain: " + domainDoc.getPathAsString());
2017                 logger.debug("Path to Workspaces root: " + workspacesRoot.getPathAsString());
2018             }
2019         } catch (Throwable e) {
2020             rollbackTransaction(repoSession);
2021             if (logger.isDebugEnabled()) {
2022                 logger.debug("Could not create tenant domain name=" + repositoryDomain.getStorageName() + " caught exception ", e);
2023             }
2024             throw e;
2025         } finally {
2026             if (repoSession != null) {
2027                 releaseRepositorySession(null, repoSession);
2028             }
2029         }
2030
2031         return domainId;
2032     }
2033
2034     @Override
2035     public String getDomainId(RepositoryDomainType repositoryDomain) throws Exception {
2036         String domainId = null;
2037         CoreSessionInterface repoSession = null;
2038
2039         String repoName = repositoryDomain.getRepositoryName();
2040         String domainStorageName = repositoryDomain.getStorageName();
2041         if (domainStorageName != null && !domainStorageName.isEmpty()) {
2042             try {
2043                 repoSession = getRepositorySession(repoName);
2044                 DocumentRef docRef = new PathRef("/" + domainStorageName);
2045                 DocumentModel domain = repoSession.getDocument(docRef);
2046                 domainId = domain.getId();
2047             } catch (Exception e) {
2048                 if (logger.isTraceEnabled()) {
2049                     logger.trace("Caught exception ", e);  // The document doesn't exist, this let's us know we need to create it
2050                 }
2051             } finally {
2052                 if (repoSession != null) {
2053                     releaseRepositorySession(null, repoSession);
2054                 }
2055             }
2056         }
2057
2058         return domainId;
2059     }
2060
2061     /*
2062      * Returns the workspaces root directory for a given domain.
2063      */
2064     private DocumentModel getWorkspacesRoot(CoreSessionInterface repoSession,
2065             String domainName) throws Exception {
2066         DocumentModel result = null;
2067
2068         String domainPath = "/" + domainName;
2069         DocumentRef parentDocRef = new PathRef(domainPath);
2070         DocumentModelList domainChildrenList = repoSession.getChildren(
2071                 parentDocRef);
2072         Iterator<DocumentModel> witer = domainChildrenList.iterator();
2073         while (witer.hasNext()) {
2074             DocumentModel childNode = witer.next();
2075             if (NuxeoUtils.Workspaces.equalsIgnoreCase(childNode.getName())) {
2076                 result = childNode;
2077                 logger.trace("Found workspaces directory at: " + result.getPathAsString());
2078                 break;
2079             }
2080         }
2081
2082         if (result == null) {
2083             throw new ClientException("Could not find workspace root directory in: "
2084                     + domainPath);
2085         }
2086
2087         return result;
2088     }
2089
2090     /* (non-Javadoc)
2091      * @see org.collectionspace.services.common.repository.RepositoryClient#createWorkspace(java.lang.String, java.lang.String)
2092      */
2093     @Override
2094     public String createWorkspace(RepositoryDomainType repositoryDomain, String workspaceName) throws Exception {
2095         CoreSessionInterface repoSession = null;
2096         String workspaceId = null;
2097         try {
2098             String repoName = repositoryDomain.getRepositoryName();
2099             repoSession = getRepositorySession(repoName);
2100
2101             String domainStorageName = repositoryDomain.getStorageName();
2102             DocumentModel parentDoc = getWorkspacesRoot(repoSession, domainStorageName);
2103             if (logger.isTraceEnabled()) {
2104                 for (String facet : parentDoc.getFacets()) {
2105                     logger.trace("Facet: " + facet);
2106                 }
2107             }
2108
2109             DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
2110                     workspaceName, NuxeoUtils.WORKSPACE_DOCUMENT_TYPE);
2111             doc.setPropertyValue("dc:title", workspaceName);
2112             doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
2113                     + workspaceName);
2114             doc = repoSession.createDocument(doc);
2115             workspaceId = doc.getId();
2116             repoSession.save();
2117             if (logger.isDebugEnabled()) {
2118                 logger.debug("Created workspace name=" + workspaceName
2119                         + " id=" + workspaceId);
2120             }
2121         } catch (Throwable e) {
2122             rollbackTransaction(repoSession);
2123             if (logger.isDebugEnabled()) {
2124                 logger.debug("createWorkspace caught exception ", e);
2125             }
2126             throw e;
2127         } finally {
2128             if (repoSession != null) {
2129                 releaseRepositorySession(null, repoSession);
2130             }
2131         }
2132         return workspaceId;
2133     }
2134
2135     /* (non-Javadoc)
2136      * @see org.collectionspace.services.common.repository.RepositoryClient#getWorkspaceId(java.lang.String, java.lang.String)
2137      */
2138     @Override
2139     @Deprecated
2140     public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
2141         String workspaceId = null;
2142
2143         CoreSessionInterface repoSession = null;
2144         try {
2145             repoSession = getRepositorySession((ServiceContext<PoxPayloadIn, PoxPayloadOut>) null);
2146             DocumentRef docRef = new PathRef(
2147                     "/" + tenantDomain
2148                     + "/" + NuxeoUtils.Workspaces
2149                     + "/" + workspaceName);
2150             DocumentModel workspace = repoSession.getDocument(docRef);
2151             workspaceId = workspace.getId();
2152         } catch (DocumentException de) {
2153             throw de;
2154         } catch (Exception e) {
2155             if (logger.isDebugEnabled()) {
2156                 logger.debug("Caught exception ", e);
2157             }
2158             throw new NuxeoDocumentException(e);
2159         } finally {
2160             if (repoSession != null) {
2161                 releaseRepositorySession(null, repoSession);
2162             }
2163         }
2164
2165         return workspaceId;
2166     }
2167
2168     @Override
2169     public CoreSessionInterface getRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
2170         return getRepositorySession(ctx, ctx.getRepositoryName(), ctx.getTimeoutSecs());
2171     }
2172
2173     public CoreSessionInterface getRepositorySession(String repoName) throws Exception {
2174         return getRepositorySession(null, repoName, ServiceContext.DEFAULT_TX_TIMEOUT);
2175     }
2176
2177     /**
2178      * Gets the repository session. - Package access only. If the 'ctx' param is
2179      * null then the repo name must be non-mull and vice-versa
2180      *
2181      * @return the repository session
2182      * @throws Exception the exception
2183      */
2184     public CoreSessionInterface getRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
2185                 String repoName,
2186                 int timeoutSeconds) throws Exception {
2187         CoreSessionInterface repoSession = null;
2188
2189         Profiler profiler = new Profiler("getRepositorySession():", 2);
2190         profiler.start();
2191         //
2192         // To get a connection to the Nuxeo repo, we need either a valid ServiceContext instance or a repository name
2193         //
2194         if (ctx != null) {
2195                 repoSession = (CoreSessionInterface) ctx.getCurrentRepositorySession(); // First see if the context already has a repo session
2196                 if (repoSession == null) {
2197                     repoName = ctx.getRepositoryName(); // Notice we are overriding the passed in 'repoName' since we have a valid service context passed in to us
2198                 }
2199         } else if (Tools.isBlank(repoName)) {
2200             String errMsg = String.format("Either a valid session context or repository name are required to get a new connection.");
2201             logger.error(errMsg);
2202             throw new Exception(errMsg);
2203         }
2204
2205         if (repoSession == null) {
2206             //
2207             // 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
2208             // just the repository name.
2209             //
2210             NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
2211             repoSession = client.openRepository(repoName, timeoutSeconds);
2212         } else {
2213             if (logger.isTraceEnabled() == true) {
2214                 logger.trace("Reusing the current context's repository session.");
2215             }
2216         }
2217         //
2218         // Debugging only code
2219         //
2220                 if (logger.isTraceEnabled()) {
2221                         try {
2222                                 if (logger.isTraceEnabled()) {
2223                                         logger.trace("Testing call to getRepository() repository root: " + repoSession.getRootDocument());
2224                                 }
2225                         } catch (Throwable e) {
2226                                 logger.trace("Test call to Nuxeo's getRepository() repository root failed", e);
2227                         }
2228                 }
2229
2230         profiler.stop();
2231
2232         if (ctx != null) {
2233             ctx.setCurrentRepositorySession(repoSession); // For reusing, save the repository session in the current service context.  The context will reference count it.
2234         }
2235
2236         return repoSession;
2237     }
2238
2239     /**
2240      * Release repository session. - Package access only.
2241      *
2242      * @param repoSession the repo session
2243      */
2244     @Override
2245     public void releaseRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, Object repositorySession) throws TransactionException {
2246         try {
2247                 CoreSessionInterface repoSession = (CoreSessionInterface)repositorySession;
2248             NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
2249             // release session
2250             if (ctx != null) {
2251                 ctx.clearCurrentRepositorySession(); //clear the current context of the now closed repo session
2252                 if (ctx.getCurrentRepositorySession() == null) {
2253                     client.releaseRepository(repoSession); //release the repo session if the service context's ref count is zeo.
2254                 }
2255             } else {
2256                 client.releaseRepository(repoSession); //repo session was acquired without a service context
2257             }
2258         } catch (TransactionRuntimeException tre) {
2259                 String causeMsg = null;
2260                 Throwable cause = tre.getCause();
2261                 if (cause != null) {
2262                         causeMsg = cause.getMessage();
2263                 }
2264
2265             TransactionException te; // a CollectionSpace specific tx exception
2266             if (causeMsg != null) {
2267                 te = new TransactionException(causeMsg, tre);
2268             } else {
2269                 te = new TransactionException(tre);
2270             }
2271
2272             logger.error(te.getMessage(), tre); // Log the standard transaction exception message, plus an exception-specific stack trace
2273             throw te;
2274         } catch (Exception e) {
2275             logger.error("Could not close the repository session.", e);
2276             // no need to throw this service specific exception
2277         }
2278     }
2279
2280     @Override
2281     public void doWorkflowTransition(ServiceContext ctx, String id,
2282             DocumentHandler handler, TransitionDef transitionDef)
2283             throws BadRequestException, DocumentNotFoundException,
2284             DocumentException {
2285         // 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
2286     }
2287
2288     private String handleProvidedStartingWildcard(String partialTerm) {
2289         if (Tools.notBlank(partialTerm)) {
2290             if (partialTerm.substring(0, 1).equals(USER_SUPPLIED_WILDCARD)) {
2291                 StringBuffer buffer = new StringBuffer(partialTerm);
2292                 buffer.setCharAt(0, JDBCTools.SQL_WILDCARD.charAt(0));
2293                 partialTerm = buffer.toString();
2294             }
2295         }
2296         return partialTerm;
2297     }
2298
2299     /**
2300      * Replaces user-supplied wildcards with SQL wildcards, in a partial term
2301      * matching search expression.
2302      *
2303      * The scope of this replacement excludes the beginning character
2304      * in that search expression, as that character is treated specially.
2305      *
2306      * @param partialTerm
2307      * @return the partial term, with any user-supplied wildcards replaced
2308      * by SQL wildcards.
2309      */
2310     private String subtituteWildcardsInPartialTerm(String partialTerm) {
2311         if (Tools.isBlank(partialTerm)) {
2312             return partialTerm;
2313         }
2314         if (! partialTerm.contains(USER_SUPPLIED_WILDCARD)) {
2315             return partialTerm;
2316         }
2317         int len = partialTerm.length();
2318         // Partial term search expressions of 2 or fewer characters
2319         // currently aren't amenable to the use of wildcards
2320         if (len <= 2)  {
2321             logger.warn("Partial term match search expression of just 1-2 characters in length contains a user-supplied wildcard: " + partialTerm);
2322             logger.warn("Will handle that character as a literal value, rather than as a wildcard ...");
2323             return partialTerm;
2324         }
2325         return partialTerm.substring(0, 1) // first char
2326                 + partialTerm.substring(1, len).replaceAll(USER_SUPPLIED_WILDCARD_REGEX, JDBCTools.SQL_WILDCARD);
2327
2328     }
2329
2330     private int getMaxItemsLimitOnJdbcQueries(String maxListItemsLimit) {
2331         final int DEFAULT_ITEMS_LIMIT = 40;
2332         if (maxListItemsLimit == null) {
2333             return DEFAULT_ITEMS_LIMIT;
2334         }
2335         int itemsLimit;
2336         try {
2337             itemsLimit = Integer.parseInt(maxListItemsLimit);
2338             if (itemsLimit < 1) {
2339                 logger.warn("Value of configuration setting "
2340                         + IQueryManager.MAX_LIST_ITEMS_RETURNED_LIMIT_ON_JDBC_QUERIES
2341                         + " must be a positive integer; invalid current value is " + maxListItemsLimit);
2342                 logger.warn("Reverting to default value of " + DEFAULT_ITEMS_LIMIT);
2343                 itemsLimit = DEFAULT_ITEMS_LIMIT;
2344             }
2345         } catch (NumberFormatException nfe) {
2346             logger.warn("Value of configuration setting "
2347                         + IQueryManager.MAX_LIST_ITEMS_RETURNED_LIMIT_ON_JDBC_QUERIES
2348                         + " must be a positive integer; invalid current value is " + maxListItemsLimit);
2349             logger.warn("Reverting to default value of " + DEFAULT_ITEMS_LIMIT);
2350             itemsLimit = DEFAULT_ITEMS_LIMIT;
2351         }
2352         return itemsLimit;
2353     }
2354
2355     /**
2356      * Identifies whether a restriction on tenant ID - to return only records
2357      * pertaining to the current tenant - is required in a JDBC query.
2358      *
2359      * @param tenantBinding a tenant binding configuration.
2360      * @param ctx a service context.
2361      * @return true if a restriction on tenant ID is required in the query;
2362      * false if a restriction is not required.
2363      */
2364     private boolean restrictJDBCQueryByTenantID(TenantBindingType tenantBinding, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
2365         boolean restrict = true;
2366         // If data for the current service, in the current tenant, is isolated
2367         // within its own separate, per-tenant repository, as contrasted with
2368         // being intermingled with other tenants' data in the default repository,
2369         // no restriction on Tenant ID is required in the query.
2370         String repositoryDomainName = ConfigUtils.getRepositoryName(tenantBinding, ctx.getRepositoryDomainName());
2371         if (!(repositoryDomainName.equals(ConfigUtils.DEFAULT_NUXEO_REPOSITORY_NAME))) {
2372             restrict = false;
2373         }
2374         // If a configuration setting for this tenant identifies that JDBC
2375         // queries should not be restricted by tenant ID (perhaps because
2376         // there is always expected to be only one tenant's data present in
2377         // the system), no restriction on Tenant ID is required in the query.
2378         String queriesRestrictedByTenantId = TenantBindingUtils.getPropertyValue(tenantBinding,
2379                 IQueryManager.JDBC_QUERIES_ARE_TENANT_ID_RESTRICTED);
2380         if (Tools.notBlank(queriesRestrictedByTenantId) &&
2381                 queriesRestrictedByTenantId.equalsIgnoreCase(Boolean.FALSE.toString())) {
2382             restrict = false;
2383         }
2384         return restrict;
2385     }
2386
2387     private void rollbackTransaction(CoreSessionInterface repoSession) {
2388         if (repoSession != null) {
2389                 repoSession.setTransactionRollbackOnly();
2390         }
2391     }
2392
2393     /**
2394      * Should never get called.
2395      */
2396         @Override
2397         public boolean delete(ServiceContext ctx, Object entityFound, DocumentHandler handler)
2398                         throws DocumentNotFoundException, DocumentException {
2399                 throw new UnsupportedOperationException();
2400         }
2401 }