2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 package org.collectionspace.services.nuxeo.client.java;
20 import java.util.Hashtable;
21 import java.util.List;
22 import java.util.UUID;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 import java.util.regex.PatternSyntaxException;
27 import org.collectionspace.services.client.IQueryManager;
28 import org.collectionspace.services.client.PoxPayloadIn;
29 import org.collectionspace.services.client.PoxPayloadOut;
30 import org.collectionspace.services.common.context.ServiceContext;
31 import org.collectionspace.services.common.datetime.GregorianCalendarDateTimeUtils;
32 import org.collectionspace.services.common.query.QueryContext;
33 import org.collectionspace.services.common.repository.RepositoryClient;
34 import org.collectionspace.services.common.profile.Profiler;
35 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
37 import org.collectionspace.services.common.document.BadRequestException;
38 import org.collectionspace.services.common.document.DocumentException;
39 import org.collectionspace.services.common.document.DocumentFilter;
40 import org.collectionspace.services.common.document.DocumentHandler;
41 import org.collectionspace.services.common.document.DocumentNotFoundException;
42 import org.collectionspace.services.common.document.DocumentHandler.Action;
43 import org.collectionspace.services.common.document.DocumentWrapper;
44 import org.collectionspace.services.common.document.DocumentWrapperImpl;
46 import org.nuxeo.common.utils.IdUtils;
47 import org.nuxeo.ecm.core.api.ClientException;
48 import org.nuxeo.ecm.core.api.DocumentModel;
49 import org.nuxeo.ecm.core.api.DocumentModelList;
50 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
51 import org.nuxeo.ecm.core.api.DocumentRef;
52 import org.nuxeo.ecm.core.api.IdRef;
53 import org.nuxeo.ecm.core.api.PathRef;
54 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
55 import org.nuxeo.ecm.core.client.NuxeoClient;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * RepositoryJavaClient is used to perform CRUD operations on documents in Nuxeo
62 * repository using Remote Java APIs. It uses @see DocumentHandler as IOHandler
65 * $LastChangedRevision: $ $LastChangedDate: $
67 public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn, PoxPayloadOut> {
70 private final Logger logger = LoggerFactory.getLogger(RepositoryJavaClientImpl.class);
71 // private final Logger profilerLogger = LoggerFactory.getLogger("remperf");
72 // private String foo = Profiler.createLogger();
74 // Regular expressions pattern for identifying valid ORDER BY clauses.
75 // FIXME: Currently supports only USASCII word characters in field names.
76 final String ORDER_BY_CLAUSE_REGEX = "\\w+(_\\w+)?:\\w+( ASC| DESC)?(, \\w+(_\\w+)?:\\w+( ASC| DESC)?)*";
79 * Instantiates a new repository java client impl.
81 public RepositoryJavaClientImpl() {
87 * Sets the collection space core values.
90 * @param documentModel the document model
91 * @throws ClientException the client exception
93 private void setCollectionSpaceCoreValues(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
94 DocumentModel documentModel,
95 Action action) throws ClientException {
97 // Add the CSID to the DublinCore title so we can see the CSID in the default
101 documentModel.setProperty("dublincore",
103 documentModel.getName());
104 } catch (Exception x) {
105 if (logger.isWarnEnabled() == true) {
106 logger.warn("Could not set the Dublin Core 'title' field on document CSID:" +
107 documentModel.getName());
111 // Add the tenant ID value to the new entity
113 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
114 DocumentModelHandler.COLLECTIONSPACE_CORE_TENANTID,
117 String now = GregorianCalendarDateTimeUtils.timestampUTC();
121 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
122 DocumentModelHandler.COLLECTIONSPACE_CORE_CREATED_AT,
124 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
125 DocumentModelHandler.COLLECTIONSPACE_CORE_UPDATED_AT,
129 documentModel.setProperty(DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA,
130 DocumentModelHandler.COLLECTIONSPACE_CORE_UPDATED_AT,
139 * create document in the Nuxeo repository
141 * @param ctx service context under which this method is invoked
143 * should be used by the caller to provide and transform the
145 * @return id in repository of the newly created document
146 * @throws DocumentException
149 public String create(ServiceContext ctx,
150 DocumentHandler handler) throws BadRequestException,
153 if (ctx.getDocumentType() == null) {
154 throw new IllegalArgumentException(
155 "RepositoryJavaClient.create: docType is missing");
157 if (handler == null) {
158 throw new IllegalArgumentException(
159 "RepositoryJavaClient.create: handler is missing");
161 String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
162 if (nuxeoWspaceId == null) {
163 throw new DocumentNotFoundException(
164 "Unable to find workspace for service " + ctx.getServiceName()
165 + " check if the workspace exists in the Nuxeo repository");
167 RepositoryInstance repoSession = null;
169 handler.prepare(Action.CREATE);
170 repoSession = getRepositorySession();
171 DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId);
172 DocumentModel wspaceDoc = repoSession.getDocument(nuxeoWspace);
173 String wspacePath = wspaceDoc.getPathAsString();
174 //give our own ID so PathRef could be constructed later on
175 String id = IdUtils.generateId(UUID.randomUUID().toString());
176 // create document model
177 DocumentModel doc = repoSession.createDocumentModel(wspacePath, id,
178 ctx.getDocumentType());
179 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
180 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
181 handler.handle(Action.CREATE, wrapDoc);
182 // create document with documentmodel
183 setCollectionSpaceCoreValues(ctx, doc, Action.CREATE);
184 doc = repoSession.createDocument(doc);
186 // TODO for sub-docs need to call into the handler to let it deal with subitems. Pass in the id,
187 // and assume the handler has the state it needs (doc fragments).
188 handler.complete(Action.CREATE, wrapDoc);
190 } catch (BadRequestException bre) {
192 } catch (Exception e) {
193 if (logger.isDebugEnabled()) {
194 logger.debug("Caught exception ", e);
196 throw new DocumentException(e);
198 if (repoSession != null) {
199 releaseRepositorySession(repoSession);
206 * get document from the Nuxeo repository
207 * @param ctx service context under which this method is invoked
209 * of the document to retrieve
211 * should be used by the caller to provide and transform the
213 * @throws DocumentException
216 public void get(ServiceContext ctx, String id, DocumentHandler handler)
217 throws DocumentNotFoundException, DocumentException {
219 if (handler == null) {
220 throw new IllegalArgumentException(
221 "RepositoryJavaClient.get: handler is missing");
223 RepositoryInstance repoSession = null;
226 handler.prepare(Action.GET);
227 repoSession = getRepositorySession();
228 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
229 DocumentModel doc = null;
231 doc = repoSession.getDocument(docRef);
232 } catch (ClientException ce) {
233 String msg = "could not find document with id=" + id;
234 logger.error(msg, ce);
235 throw new DocumentNotFoundException(msg, ce);
237 //set reposession to handle the document
238 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
239 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
240 handler.handle(Action.GET, wrapDoc);
241 handler.complete(Action.GET, wrapDoc);
242 } catch (IllegalArgumentException iae) {
244 } catch (DocumentException de) {
246 } catch (Exception e) {
247 if (logger.isDebugEnabled()) {
248 logger.debug("Caught exception ", e);
250 throw new DocumentException(e);
252 if (repoSession != null) {
253 releaseRepositorySession(repoSession);
259 * get document from the Nuxeo repository, using the docFilter params.
260 * @param ctx service context under which this method is invoked
262 * should be used by the caller to provide and transform the
263 * document. Handler must have a docFilter set to return a single item.
264 * @throws DocumentException
267 public void get(ServiceContext ctx, DocumentHandler handler)
268 throws DocumentNotFoundException, DocumentException {
269 QueryContext queryContext = new QueryContext(ctx, handler);
270 RepositoryInstance repoSession = null;
273 handler.prepare(Action.GET);
274 repoSession = getRepositorySession();
276 DocumentModelList docList = null;
277 // force limit to 1, and ignore totalSize
278 String query = buildNXQLQuery(queryContext);
279 docList = repoSession.query(query, null, 1, 0, false);
280 if (docList.size() != 1) {
281 throw new DocumentNotFoundException("No document found matching filter params.");
283 DocumentModel doc = docList.get(0);
285 if (logger.isDebugEnabled()) {
286 logger.debug("Executed NXQL query: " + query);
289 //set reposession to handle the document
290 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
291 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
292 handler.handle(Action.GET, wrapDoc);
293 handler.complete(Action.GET, wrapDoc);
294 } catch (IllegalArgumentException iae) {
296 } catch (DocumentException de) {
298 } catch (Exception e) {
299 if (logger.isDebugEnabled()) {
300 logger.debug("Caught exception ", e);
302 throw new DocumentException(e);
304 if (repoSession != null) {
305 releaseRepositorySession(repoSession);
310 private static String getByNameWhereClause(String csid) {
311 String result = null;
314 result = "ecm:name = " + "\'" + csid + "\'";
321 * Get wrapped documentModel from the Nuxeo repository. The search is restricted to the workspace
322 * of the current context.
324 * @param ctx service context under which this method is invoked
326 * of the document to retrieve
327 * @throws DocumentException
330 public DocumentWrapper<DocumentModel> getDoc(
331 ServiceContext ctx, String csid)
332 throws DocumentNotFoundException, DocumentException {
333 RepositoryInstance repoSession = null;
334 DocumentWrapper<DocumentModel> wrapDoc = null;
337 repoSession = getRepositorySession();
338 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
339 DocumentModel doc = null;
341 doc = repoSession.getDocument(docRef);
342 } catch (ClientException ce) {
343 String msg = "could not find document with id=" + csid;
344 logger.error(msg, ce);
345 throw new DocumentNotFoundException(msg, ce);
347 wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
348 } catch (IllegalArgumentException iae) {
350 } catch (DocumentException de) {
352 } catch (Exception e) {
353 if (logger.isDebugEnabled()) {
354 logger.debug("Caught exception ", e);
356 throw new DocumentException(e);
358 if (repoSession != null) {
359 releaseRepositorySession(repoSession);
366 * find wrapped documentModel from the Nuxeo repository
367 * @param ctx service context under which this method is invoked
368 * @param whereClause where NXQL where clause to get the document
369 * @throws DocumentException
372 public DocumentWrapper<DocumentModel> findDoc(
373 ServiceContext ctx, String whereClause)
374 throws DocumentNotFoundException, DocumentException {
375 RepositoryInstance repoSession = null;
376 DocumentWrapper<DocumentModel> wrapDoc = null;
379 QueryContext queryContext = new QueryContext(ctx, whereClause);
380 repoSession = getRepositorySession();
381 DocumentModelList docList = null;
382 // force limit to 1, and ignore totalSize
383 String query = buildNXQLQuery(queryContext);
384 docList = repoSession.query(query,
389 if (docList.size() != 1) {
390 if (logger.isDebugEnabled()) {
391 logger.debug("findDoc: Query found: " + docList.size() + " items.");
392 logger.debug(" Query: " + query);
394 throw new DocumentNotFoundException("No document found matching filter params.");
396 DocumentModel doc = docList.get(0);
397 wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
398 } catch (IllegalArgumentException iae) {
400 } catch (DocumentException de) {
402 } catch (Exception e) {
403 if (logger.isDebugEnabled()) {
404 logger.debug("Caught exception ", e);
406 throw new DocumentException(e);
408 if (repoSession != null) {
409 releaseRepositorySession(repoSession);
416 * find doc and return CSID from the Nuxeo repository
417 * @param ctx service context under which this method is invoked
418 * @param whereClause where NXQL where clause to get the document
419 * @throws DocumentException
422 public String findDocCSID(
423 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String whereClause)
424 throws DocumentNotFoundException, DocumentException {
427 DocumentWrapper<DocumentModel> wrapDoc = findDoc(ctx, whereClause);
428 DocumentModel docModel = wrapDoc.getWrappedObject();
429 csid = NuxeoUtils.getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
430 } catch (DocumentNotFoundException dnfe) {
432 } catch (IllegalArgumentException iae) {
434 } catch (DocumentException de) {
436 } catch (Exception e) {
437 if (logger.isDebugEnabled()) {
438 logger.debug("Caught exception ", e);
440 throw new DocumentException(e);
446 * Find a list of documentModels from the Nuxeo repository
447 * @param docTypes a list of DocType names to match
448 * @param whereClause where the clause to qualify on
452 public DocumentWrapper<DocumentModelList> findDocs(
453 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
454 List<String> docTypes,
456 int pageSize, int pageNum, boolean computeTotal)
457 throws DocumentNotFoundException, DocumentException {
458 RepositoryInstance repoSession = null;
459 DocumentWrapper<DocumentModelList> wrapDoc = null;
462 if (docTypes == null || docTypes.size() < 1) {
463 throw new DocumentNotFoundException(
464 "findDocs must specify at least one DocumentType.");
466 repoSession = getRepositorySession();
467 DocumentModelList docList = null;
468 // force limit to 1, and ignore totalSize
469 QueryContext queryContext = new QueryContext(ctx, whereClause);
470 String query = buildNXQLQuery(docTypes, queryContext);
471 docList = repoSession.query(query, null, pageSize, pageNum, computeTotal);
472 wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
473 } catch (IllegalArgumentException iae) {
475 } catch (Exception e) {
476 if (logger.isDebugEnabled()) {
477 logger.debug("Caught exception ", e);
479 throw new DocumentException(e);
481 if (repoSession != null) {
482 releaseRepositorySession(repoSession);
489 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
492 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
493 throws DocumentNotFoundException, DocumentException {
494 if (handler == null) {
495 throw new IllegalArgumentException(
496 "RepositoryJavaClient.getAll: handler is missing");
499 RepositoryInstance repoSession = null;
502 handler.prepare(Action.GET_ALL);
503 repoSession = getRepositorySession();
504 DocumentModelList docModelList = new DocumentModelListImpl();
505 //FIXME: Should be using NuxeoUtils.createPathRef for security reasons
506 for (String csid : csidList) {
507 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
508 DocumentModel docModel = repoSession.getDocument(docRef);
509 docModelList.add(docModel);
512 //set reposession to handle the document
513 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
514 DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docModelList);
515 handler.handle(Action.GET_ALL, wrapDoc);
516 handler.complete(Action.GET_ALL, wrapDoc);
517 } catch (DocumentException de) {
519 } catch (Exception e) {
520 if (logger.isDebugEnabled()) {
521 logger.debug("Caught exception ", e);
523 throw new DocumentException(e);
525 if (repoSession != null) {
526 releaseRepositorySession(repoSession);
532 * getAll get all documents for an entity entity service from the Nuxeo
535 * @param ctx service context under which this method is invoked
537 * should be used by the caller to provide and transform the
539 * @throws DocumentException
542 public void getAll(ServiceContext ctx, DocumentHandler handler)
543 throws DocumentNotFoundException, DocumentException {
544 if (handler == null) {
545 throw new IllegalArgumentException(
546 "RepositoryJavaClient.getAll: handler is missing");
548 String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
549 if (nuxeoWspaceId == null) {
550 throw new DocumentNotFoundException(
551 "Unable to find workspace for service "
552 + ctx.getServiceName()
553 + " check if the workspace exists in the Nuxeo repository");
555 RepositoryInstance repoSession = null;
558 handler.prepare(Action.GET_ALL);
559 repoSession = getRepositorySession();
560 DocumentRef wsDocRef = new IdRef(nuxeoWspaceId);
561 DocumentModelList docList = repoSession.getChildren(wsDocRef);
562 //set reposession to handle the document
563 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
564 DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
565 handler.handle(Action.GET_ALL, wrapDoc);
566 handler.complete(Action.GET_ALL, wrapDoc);
567 } catch (DocumentException de) {
569 } catch (Exception e) {
570 if (logger.isDebugEnabled()) {
571 logger.debug("Caught exception ", e);
573 throw new DocumentException(e);
575 if (repoSession != null) {
576 releaseRepositorySession(repoSession);
581 private boolean isClauseEmpty(String theString) {
582 boolean result = true;
583 if (theString != null && !theString.isEmpty()) {
590 * A method to find a CollectionSpace document (of any type) given just a service context and
591 * its CSID. A search across *all* service workspaces (within a given tenant context) is performed to find
594 * This query searches Nuxeo's Hierarchy table where our CSIDs are stored in the "name" column.
597 public DocumentWrapper<DocumentModel> getDocFromCsid(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
600 DocumentWrapper<DocumentModel> result = null;
601 RepositoryInstance repoSession = getRepositorySession();
602 DocumentModelList docModelList = null;
604 // Set of query context using the current service context, but change the document type
605 // to be the base Nuxeo document type so we can look for the document across service workspaces
607 QueryContext queryContext = new QueryContext(ctx, getByNameWhereClause(csid));
608 queryContext.setDocType(NuxeoUtils.BASE_DOCUMENT_TYPE);
610 // Since we're doing a query, we get back a list so we need to make sure there is only
611 // a single result since CSID values are supposed to be unique.
612 String query = buildNXQLQuery(queryContext);
613 docModelList = repoSession.query(query);
614 long resultSize = docModelList.totalSize();
615 if (resultSize == 1) {
616 result = new DocumentWrapperImpl<DocumentModel>(docModelList.get(0));
617 } else if (resultSize > 1) {
618 throw new DocumentException("Found more than 1 document with CSID = " + csid);
626 * getFiltered get all documents for an entity service from the Document repository,
627 * given filter parameters specified by the handler.
628 * @param ctx service context under which this method is invoked
629 * @param handler should be used by the caller to provide and transform the document
630 * @throws DocumentNotFoundException if workspace not found
631 * @throws DocumentException
634 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
635 throws DocumentNotFoundException, DocumentException {
637 DocumentFilter filter = handler.getDocumentFilter();
638 String oldOrderBy = filter.getOrderByClause();
639 if (isClauseEmpty(oldOrderBy) == true){
640 filter.setOrderByClause(DocumentFilter.ORDER_BY_LAST_UPDATED); //per http://issues.collectionspace.org/browse/CSPACE-705
642 QueryContext queryContext = new QueryContext(ctx, handler);
643 RepositoryInstance repoSession = null;
645 handler.prepare(Action.GET_ALL);
646 repoSession = getRepositorySession();
647 DocumentModelList docList = null;
648 String query = buildNXQLQuery(queryContext);
650 if (logger.isDebugEnabled()) {
651 logger.debug("Executing NXQL query: " + query.toString());
654 // If we have limit and/or offset, then pass true to get totalSize
655 // in returned DocumentModelList.
656 Profiler profiler = new Profiler(this, 2);
657 profiler.log("Executing NXQL query: " + query.toString());
659 if ((queryContext.getDocFilter().getOffset() > 0) || (queryContext.getDocFilter().getPageSize() > 0)) {
660 docList = repoSession.query(query, null,
661 queryContext.getDocFilter().getPageSize(), queryContext.getDocFilter().getOffset(), true);
663 docList = repoSession.query(query);
667 //set repoSession to handle the document
668 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
669 DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
670 handler.handle(Action.GET_ALL, wrapDoc);
671 handler.complete(Action.GET_ALL, wrapDoc);
672 } catch (DocumentException de) {
674 } catch (Exception e) {
675 if (logger.isDebugEnabled()) {
676 logger.debug("Caught exception ", e);
678 throw new DocumentException(e);
680 if (repoSession != null) {
681 releaseRepositorySession(repoSession);
687 * update given document in the Nuxeo repository
689 * @param ctx service context under which this method is invoked
693 * should be used by the caller to provide and transform the
695 * @throws DocumentException
698 public void update(ServiceContext ctx, String id, DocumentHandler handler)
699 throws BadRequestException, DocumentNotFoundException,
701 if (handler == null) {
702 throw new IllegalArgumentException(
703 "RepositoryJavaClient.update: handler is missing");
705 RepositoryInstance repoSession = null;
707 handler.prepare(Action.UPDATE);
708 repoSession = getRepositorySession();
709 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
710 DocumentModel doc = null;
712 doc = repoSession.getDocument(docRef);
713 } catch (ClientException ce) {
714 String msg = "Could not find document to update with id=" + id;
715 logger.error(msg, ce);
716 throw new DocumentNotFoundException(msg, ce);
718 //set reposession to handle the document
719 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
720 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
721 handler.handle(Action.UPDATE, wrapDoc);
722 setCollectionSpaceCoreValues(ctx, doc, Action.UPDATE);
723 repoSession.saveDocument(doc);
725 handler.complete(Action.UPDATE, wrapDoc);
726 } catch (BadRequestException bre) {
728 } catch (DocumentException de) {
730 } catch (Exception e) {
731 if (logger.isDebugEnabled()) {
732 logger.debug("Caught exception ", e);
734 throw new DocumentException(e);
736 if (repoSession != null) {
737 releaseRepositorySession(repoSession);
743 * delete a document from the Nuxeo repository
744 * @param ctx service context under which this method is invoked
747 * @throws DocumentException
750 public void delete(ServiceContext ctx, String id) throws DocumentNotFoundException,
753 if (logger.isDebugEnabled()) {
754 logger.debug("deleting document with id=" + id);
756 RepositoryInstance repoSession = null;
758 repoSession = getRepositorySession();
759 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
761 repoSession.removeDocument(docRef);
762 } catch (ClientException ce) {
763 String msg = "could not find document to delete with id=" + id;
764 logger.error(msg, ce);
765 throw new DocumentNotFoundException(msg, ce);
768 } catch (DocumentException de) {
770 } catch (Exception e) {
771 if (logger.isDebugEnabled()) {
772 logger.debug("Caught exception ", e);
774 throw new DocumentException(e);
776 if (repoSession != null) {
777 releaseRepositorySession(repoSession);
783 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
786 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
787 throws DocumentNotFoundException, DocumentException {
788 throw new UnsupportedOperationException();
792 public Hashtable<String, String> retrieveWorkspaceIds(String domainName) throws Exception {
793 return NuxeoConnector.getInstance().retrieveWorkspaceIds(domainName);
797 public String createDomain(String domainName) throws Exception {
798 RepositoryInstance repoSession = null;
799 String domainId = null;
801 repoSession = getRepositorySession();
802 DocumentRef parentDocRef = new PathRef("/");
803 DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
804 DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
805 domainName, "Domain");
806 doc.setPropertyValue("dc:title", domainName);
807 doc.setPropertyValue("dc:description", "A CollectionSpace domain "
809 doc = repoSession.createDocument(doc);
810 domainId = doc.getId();
812 if (logger.isDebugEnabled()) {
813 logger.debug("created tenant domain name=" + domainName
814 + " id=" + domainId);
816 } catch (Exception e) {
817 if (logger.isDebugEnabled()) {
818 logger.debug("createTenantSpace caught exception ", e);
822 if (repoSession != null) {
823 releaseRepositorySession(repoSession);
830 public String getDomainId(String domainName) throws Exception {
831 String domainId = null;
832 RepositoryInstance repoSession = null;
834 repoSession = getRepositorySession();
835 DocumentRef docRef = new PathRef(
837 DocumentModel domain = repoSession.getDocument(docRef);
838 domainId = domain.getId();
839 } catch (Exception e) {
840 if (logger.isDebugEnabled()) {
841 logger.debug("Caught exception ", e);
843 //there is no way to identify if document does not exist due to
844 //lack of typed exception for getDocument method
847 if (repoSession != null) {
848 releaseRepositorySession(repoSession);
855 * @see org.collectionspace.services.common.repository.RepositoryClient#createWorkspace(java.lang.String, java.lang.String)
858 public String createWorkspace(String domainName, String workspaceName) throws Exception {
859 RepositoryInstance repoSession = null;
860 String workspaceId = null;
862 repoSession = getRepositorySession();
863 DocumentRef parentDocRef = new PathRef(
865 + "/" + "workspaces");
866 DocumentModel parentDoc = repoSession.getDocument(parentDocRef);
867 DocumentModel doc = repoSession.createDocumentModel(parentDoc.getPathAsString(),
868 workspaceName, "Workspace");
869 doc.setPropertyValue("dc:title", workspaceName);
870 doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
872 doc = repoSession.createDocument(doc);
873 workspaceId = doc.getId();
875 if (logger.isDebugEnabled()) {
876 logger.debug("created workspace name=" + workspaceName
877 + " id=" + workspaceId);
879 } catch (Exception e) {
880 if (logger.isDebugEnabled()) {
881 logger.debug("createWorkspace caught exception ", e);
885 if (repoSession != null) {
886 releaseRepositorySession(repoSession);
893 * @see org.collectionspace.services.common.repository.RepositoryClient#getWorkspaceId(java.lang.String, java.lang.String)
896 public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
897 String workspaceId = null;
898 RepositoryInstance repoSession = null;
900 repoSession = getRepositorySession();
901 DocumentRef docRef = new PathRef(
904 + "/" + workspaceName);
905 DocumentModel workspace = repoSession.getDocument(docRef);
906 workspaceId = workspace.getId();
907 } catch (DocumentException de) {
909 } catch (Exception e) {
910 if (logger.isDebugEnabled()) {
911 logger.debug("Caught exception ", e);
913 throw new DocumentException(e);
915 if (repoSession != null) {
916 releaseRepositorySession(repoSession);
923 * Append a WHERE clause to the NXQL query.
925 * @param query The NXQL query to which the WHERE clause will be appended.
926 * @param queryContext The query context, which provides the WHERE clause to append.
928 private final void appendNXQLWhere(StringBuilder query, QueryContext queryContext) {
930 // Restrict search to a specific Nuxeo domain
931 // TODO This is a slow method for tenant-filter
932 // We should make this a property that is indexed.
934 // query.append(" WHERE ecm:path STARTSWITH '/" + queryContext.domain + "'");
937 // Restrict search to the current tenant ID. Is the domain path filter (above) still needed?
939 query.append(/*IQueryManager.SEARCH_QUALIFIER_AND +*/ " WHERE " + DocumentModelHandler.COLLECTIONSPACE_CORE_SCHEMA + ":"
940 + DocumentModelHandler.COLLECTIONSPACE_CORE_TENANTID
941 + " = " + queryContext.getTenantId());
943 // Finally, append the incoming where clause
945 String whereClause = queryContext.getWhereClause();
946 if (whereClause != null && ! whereClause.trim().isEmpty()) {
947 // Due to an apparent bug/issue in how Nuxeo translates the NXQL query string
948 // into SQL, we need to parenthesize our 'where' clause
949 query.append(IQueryManager.SEARCH_QUALIFIER_AND + "(" + whereClause + ")");
952 // Please lookup this use in Nuxeo support and document here
954 query.append(IQueryManager.SEARCH_QUALIFIER_AND + "ecm:isProxy = 0");
958 * Append an ORDER BY clause to the NXQL query.
960 * @param query the NXQL query to which the ORDER BY clause will be appended.
961 * @param queryContext the query context, which provides the ORDER BY clause to append.
963 * @throws DocumentException if the supplied value of the orderBy clause is not valid.
966 private final void appendNXQLOrderBy(StringBuilder query, QueryContext queryContext)
968 String orderByClause = queryContext.getOrderByClause();
969 if (orderByClause != null && ! orderByClause.trim().isEmpty()) {
970 if (isValidOrderByClause(orderByClause)) {
971 query.append(" ORDER BY ");
972 query.append(orderByClause);
974 throw new DocumentException("Invalid format in sort request '" + orderByClause
975 + "': must be schema_name:fieldName followed by optional sort order (' ASC' or ' DESC').");
981 * Identifies whether the ORDER BY clause is valid.
983 * @param orderByClause the ORDER BY clause.
985 * @return true if the ORDER BY clause is valid;
986 * false if it is not.
988 private final boolean isValidOrderByClause(String orderByClause) {
989 boolean isValidClause = false;
991 Pattern orderByPattern = Pattern.compile(ORDER_BY_CLAUSE_REGEX);
992 Matcher orderByMatcher = orderByPattern.matcher(orderByClause);
993 if (orderByMatcher.matches()) {
994 isValidClause = true;
996 } catch (PatternSyntaxException pe) {
997 logger.warn("ORDER BY clause regex pattern '" + ORDER_BY_CLAUSE_REGEX
998 + "' could not be compiled: " + pe.getMessage());
999 // If reached, method will return a value of false.
1001 return isValidClause;
1006 * Builds an NXQL SELECT query for a single document type.
1008 * @param queryContext The query context
1009 * @return an NXQL query
1010 * @throws Exception if supplied values in the query are invalid.
1012 private final String buildNXQLQuery(QueryContext queryContext) throws Exception {
1013 StringBuilder query = new StringBuilder("SELECT * FROM ");
1014 query.append(queryContext.getDocType());
1015 appendNXQLWhere(query, queryContext);
1016 appendNXQLOrderBy(query, queryContext);
1017 return query.toString();
1021 * Builds an NXQL SELECT query across multiple document types.
1023 * @param docTypes a list of document types to be queried
1024 * @param queryContext the query context
1025 * @return an NXQL query
1027 private final String buildNXQLQuery(List<String> docTypes, QueryContext queryContext) {
1028 StringBuilder query = new StringBuilder("SELECT * FROM ");
1029 boolean fFirst = true;
1030 for (String docType : docTypes) {
1036 query.append(docType);
1038 appendNXQLWhere(query, queryContext);
1039 // FIXME add 'order by' clause here, if appropriate
1040 return query.toString();
1044 * Gets the repository session.
1046 * @return the repository session
1047 * @throws Exception the exception
1049 private RepositoryInstance getRepositorySession() throws Exception {
1050 // FIXME: is it possible to reuse repository session?
1051 // Authentication failures happen while trying to reuse the session
1052 Profiler profiler = new Profiler("getRepositorySession():", 2);
1054 NuxeoClient client = NuxeoConnector.getInstance().getClient();
1055 RepositoryInstance repoSession = client.openRepository();
1056 if (logger.isTraceEnabled()) {
1057 logger.debug("getRepository() repository root: " + repoSession.getRootDocument());
1064 * Release repository session.
1066 * @param repoSession the repo session
1068 private void releaseRepositorySession(RepositoryInstance repoSession) {
1070 NuxeoClient client = NuxeoConnector.getInstance().getClient();
1072 client.releaseRepository(repoSession);
1073 } catch (Exception e) {
1074 logger.error("Could not close the repository session", e);
1075 // no need to throw this service specific exception