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.UUID;
21 import java.util.List;
23 import org.collectionspace.services.common.context.ServiceContext;
24 import org.collectionspace.services.common.document.BadRequestException;
25 import org.collectionspace.services.common.document.DocumentException;
26 import org.collectionspace.services.common.document.DocumentFilter;
27 import org.collectionspace.services.common.document.DocumentHandler;
28 import org.collectionspace.services.common.document.DocumentNotFoundException;
29 import org.collectionspace.services.common.repository.RepositoryClient;
30 import org.collectionspace.services.common.document.DocumentHandler.Action;
31 import org.collectionspace.services.common.document.DocumentWrapper;
32 import org.collectionspace.services.common.document.DocumentWrapperImpl;
33 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
34 import org.nuxeo.common.utils.IdUtils;
35 import org.nuxeo.ecm.core.api.ClientException;
36 import org.nuxeo.ecm.core.api.DocumentModel;
37 import org.nuxeo.ecm.core.api.DocumentModelList;
38 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
39 import org.nuxeo.ecm.core.api.DocumentRef;
40 import org.nuxeo.ecm.core.api.IdRef;
41 import org.nuxeo.ecm.core.api.PathRef;
42 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
43 import org.nuxeo.ecm.core.client.NuxeoClient;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * RepositoryJavaClient is used to perform CRUD operations on documents in Nuxeo
49 * repository using Remote Java APIs. It uses @see DocumentHandler as IOHandler
52 * $LastChangedRevision: $ $LastChangedDate: $
54 public class RepositoryJavaClientImpl implements RepositoryClient {
56 private final Logger logger = LoggerFactory.getLogger(RepositoryJavaClientImpl.class);
58 public RepositoryJavaClientImpl() {
62 * create document in the Nuxeo repository
64 * @param ctx service context under which this method is invoked
66 * of the document created
68 * should be used by the caller to provide and transform the
70 * @return id in repository of the newly created document
71 * @throws DocumentException
74 public String create(ServiceContext ctx,
75 DocumentHandler handler) throws BadRequestException,
78 if (ctx.getDocumentType() == null) {
79 throw new IllegalArgumentException(
80 "RepositoryJavaClient.create: docType is missing");
82 if (handler == null) {
83 throw new IllegalArgumentException(
84 "RepositoryJavaClient.create: handler is missing");
86 String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
87 if (nuxeoWspaceId == null) {
88 throw new DocumentNotFoundException(
89 "Unable to find workspace for service " + ctx.getServiceName()
90 + " check if the workspace exists in the Nuxeo repository");
92 RepositoryInstance repoSession = null;
94 handler.prepare(Action.CREATE);
95 repoSession = getRepositorySession();
96 DocumentRef nuxeoWspace = new IdRef(nuxeoWspaceId);
97 DocumentModel wspaceDoc = repoSession.getDocument(nuxeoWspace);
98 String wspacePath = wspaceDoc.getPathAsString();
99 //give our own ID so PathRef could be constructed later on
100 String id = IdUtils.generateId(UUID.randomUUID().toString());
101 // create document model
102 DocumentModel doc = repoSession.createDocumentModel(wspacePath, id,
103 ctx.getDocumentType());
104 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
105 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
106 handler.handle(Action.CREATE, wrapDoc);
107 // create document with documentmodel
108 doc = repoSession.createDocument(doc);
110 handler.complete(Action.CREATE, wrapDoc);
112 } catch (BadRequestException bre) {
114 } catch (Exception e) {
115 if (logger.isDebugEnabled()) {
116 logger.debug("Caught exception ", e);
118 throw new DocumentException(e);
120 if (repoSession != null) {
121 releaseRepositorySession(repoSession);
128 * get document from the Nuxeo repository
129 * @param ctx service context under which this method is invoked
131 * of the document to retrieve
133 * should be used by the caller to provide and transform the
135 * @throws DocumentException
138 public void get(ServiceContext ctx, String id, DocumentHandler handler)
139 throws DocumentNotFoundException, DocumentException {
141 if (handler == null) {
142 throw new IllegalArgumentException(
143 "RepositoryJavaClient.get: handler is missing");
145 RepositoryInstance repoSession = null;
148 handler.prepare(Action.GET);
149 repoSession = getRepositorySession();
150 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
151 DocumentModel doc = null;
153 doc = repoSession.getDocument(docRef);
154 } catch (ClientException ce) {
155 String msg = "could not find document with id=" + id;
156 logger.error(msg, ce);
157 throw new DocumentNotFoundException(msg, ce);
159 //set reposession to handle the document
160 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
161 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
162 handler.handle(Action.GET, wrapDoc);
163 handler.complete(Action.GET, wrapDoc);
164 } catch (IllegalArgumentException iae) {
166 } catch (DocumentException de) {
168 } catch (Exception e) {
169 if (logger.isDebugEnabled()) {
170 logger.debug("Caught exception ", e);
172 throw new DocumentException(e);
174 if (repoSession != null) {
175 releaseRepositorySession(repoSession);
181 * get document from the Nuxeo repository, using the docFilter params.
182 * @param ctx service context under which this method is invoked
184 * should be used by the caller to provide and transform the
185 * document. Handler must have a docFilter set to return a single item.
186 * @throws DocumentException
189 public void get(ServiceContext ctx, DocumentHandler handler)
190 throws DocumentNotFoundException, DocumentException {
192 if (handler == null) {
193 throw new IllegalArgumentException(
194 "RepositoryJavaClient.get: handler is missing");
196 DocumentFilter docFilter = handler.getDocumentFilter();
197 if (docFilter == null) {
198 throw new IllegalArgumentException(
199 "RepositoryJavaClient.get: handler has no Filter specified");
201 if(docFilter.getPageSize()!= 1) {
202 logger.warn("RepositoryJavaClient.get: forcing docFilter pagesize to 1.");
204 String docType = ctx.getDocumentType();
205 if (docType == null) {
206 throw new DocumentNotFoundException(
207 "Unable to find DocumentType for service " + ctx.getServiceName());
209 String domain = ctx.getRepositoryDomainName();
210 if (domain == null) {
211 throw new DocumentNotFoundException(
212 "Unable to find Domain for service " + ctx.getServiceName());
214 RepositoryInstance repoSession = null;
217 handler.prepare(Action.GET);
218 repoSession = getRepositorySession();
220 DocumentModelList docList = null;
221 // force limit to 1, and ignore totalSize
222 String query = buildNXQLQuery(docType, docFilter.getWhereClause(), domain );
223 docList = repoSession.query( query, null, 1, 0, false);
224 if(docList.size()!=1) {
225 throw new DocumentNotFoundException("No document found matching filter params.");
227 DocumentModel doc = docList.get(0);
229 if (logger.isDebugEnabled()) {
230 logger.debug("Executed NXQL query: " + query);
233 //set reposession to handle the document
234 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
235 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
236 handler.handle(Action.GET, wrapDoc);
237 handler.complete(Action.GET, wrapDoc);
238 } catch (IllegalArgumentException iae) {
240 } catch (DocumentException de) {
242 } catch (Exception e) {
243 if (logger.isDebugEnabled()) {
244 logger.debug("Caught exception ", e);
246 throw new DocumentException(e);
248 if (repoSession != null) {
249 releaseRepositorySession(repoSession);
255 * get wrapped documentModel from the Nuxeo repository
256 * @param ctx service context under which this method is invoked
258 * of the document to retrieve
259 * @throws DocumentException
262 public DocumentWrapper<DocumentModel> getDoc(
263 ServiceContext ctx, String id)
264 throws DocumentNotFoundException, DocumentException {
265 RepositoryInstance repoSession = null;
266 DocumentWrapper<DocumentModel> wrapDoc = null;
269 repoSession = getRepositorySession();
270 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
271 DocumentModel doc = null;
273 doc = repoSession.getDocument(docRef);
274 } catch (ClientException ce) {
275 String msg = "could not find document with id=" + id;
276 logger.error(msg, ce);
277 throw new DocumentNotFoundException(msg, ce);
279 wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
280 } catch (IllegalArgumentException iae) {
282 } catch (DocumentException de) {
284 } catch (Exception e) {
285 if (logger.isDebugEnabled()) {
286 logger.debug("Caught exception ", e);
288 throw new DocumentException(e);
290 if (repoSession != null) {
291 releaseRepositorySession(repoSession);
298 * find wrapped documentModel from the Nuxeo repository
299 * @param ctx service context under which this method is invoked
300 * @param where NXQL where clause to get the document
301 * @throws DocumentException
304 public DocumentWrapper<DocumentModel> findDoc(
305 ServiceContext ctx, String where)
306 throws DocumentNotFoundException, DocumentException {
307 RepositoryInstance repoSession = null;
308 DocumentWrapper<DocumentModel> wrapDoc = null;
311 String docType = ctx.getDocumentType();
312 if (docType == null) {
313 throw new DocumentNotFoundException(
314 "Unable to find DocumentType for service " + ctx.getServiceName());
316 String domain = ctx.getRepositoryDomainName();
317 if (domain == null) {
318 throw new DocumentNotFoundException(
319 "Unable to find Domain for service " + ctx.getServiceName());
321 repoSession = getRepositorySession();
322 DocumentModelList docList = null;
323 // force limit to 1, and ignore totalSize
324 String query = buildNXQLQuery(docType, where, domain );
325 docList = repoSession.query( query, null, 1, 0, false);
326 if(docList.size()!=1) {
327 if (logger.isDebugEnabled()) {
328 logger.debug("findDoc: Query found: "+docList.size()+" items.");
329 logger.debug(" Query: " + query);
331 throw new DocumentNotFoundException("No document found matching filter params.");
333 DocumentModel doc = docList.get(0);
334 wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
335 } catch (IllegalArgumentException iae) {
337 } catch (DocumentException de) {
339 } catch (Exception e) {
340 if (logger.isDebugEnabled()) {
341 logger.debug("Caught exception ", e);
343 throw new DocumentException(e);
345 if (repoSession != null) {
346 releaseRepositorySession(repoSession);
353 * find doc and return CSID from the Nuxeo repository
354 * @param ctx service context under which this method is invoked
355 * @param where NXQL where clause to get the document
356 * @throws DocumentException
358 public String findDocCSID(
359 ServiceContext ctx, String where)
360 throws DocumentNotFoundException, DocumentException {
363 DocumentWrapper<DocumentModel> wrapDoc = findDoc(ctx, where);
364 DocumentModel docModel = wrapDoc.getWrappedObject();
365 csid = NuxeoUtils.extractId(docModel.getPathAsString());
366 } catch (DocumentNotFoundException dnfe) {
368 } catch (IllegalArgumentException iae) {
370 } catch (DocumentException de) {
372 } catch (Exception e) {
373 if (logger.isDebugEnabled()) {
374 logger.debug("Caught exception ", e);
376 throw new DocumentException(e);
382 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
383 throws DocumentNotFoundException, DocumentException {
384 if (handler == null) {
385 throw new IllegalArgumentException(
386 "RepositoryJavaClient.getAll: handler is missing");
389 RepositoryInstance repoSession = null;
392 handler.prepare(Action.GET_ALL);
393 repoSession = getRepositorySession();
394 DocumentModelList docModelList = new DocumentModelListImpl();
395 //FIXME: Should be using NuxeoUtils.createPathRef for security reasons
396 for (String csid : csidList) {
397 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, csid);
398 DocumentModel docModel = repoSession.getDocument(docRef);
399 docModelList.add(docModel);
402 //set reposession to handle the document
403 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
404 DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docModelList);
405 handler.handle(Action.GET_ALL, wrapDoc);
406 handler.complete(Action.GET_ALL, wrapDoc);
407 } catch (DocumentException de) {
409 } catch (Exception e) {
410 if (logger.isDebugEnabled()) {
411 logger.debug("Caught exception ", e);
413 throw new DocumentException(e);
415 if (repoSession != null) {
416 releaseRepositorySession(repoSession);
422 * getAll get all documents for an entity entity service from the Nuxeo
425 * @param ctx service context under which this method is invoked
427 * should be used by the caller to provide and transform the
429 * @throws DocumentException
432 public void getAll(ServiceContext ctx, DocumentHandler handler)
433 throws DocumentNotFoundException, DocumentException {
434 if (handler == null) {
435 throw new IllegalArgumentException(
436 "RepositoryJavaClient.getAll: handler is missing");
438 String nuxeoWspaceId = ctx.getRepositoryWorkspaceId();
439 if (nuxeoWspaceId == null) {
440 throw new DocumentNotFoundException(
441 "Unable to find workspace for service "
442 + ctx.getServiceName()
443 + " check if the workspace exists in the Nuxeo repository");
445 RepositoryInstance repoSession = null;
448 handler.prepare(Action.GET_ALL);
449 repoSession = getRepositorySession();
450 DocumentRef wsDocRef = new IdRef(nuxeoWspaceId);
451 DocumentModelList docList = repoSession.getChildren(wsDocRef);
452 //set reposession to handle the document
453 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
454 DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
455 handler.handle(Action.GET_ALL, wrapDoc);
456 handler.complete(Action.GET_ALL, wrapDoc);
457 } catch (DocumentException de) {
459 } catch (Exception e) {
460 if (logger.isDebugEnabled()) {
461 logger.debug("Caught exception ", e);
463 throw new DocumentException(e);
465 if (repoSession != null) {
466 releaseRepositorySession(repoSession);
472 * getFiltered get all documents for an entity service from the Document repository,
473 * given filter parameters specified by the handler.
474 * @param ctx service context under which this method is invoked
475 * @param handler should be used by the caller to provide and transform the document
476 * @throws DocumentNotFoundException if workspace not found
477 * @throws DocumentException
479 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
480 throws DocumentNotFoundException, DocumentException {
481 if (handler == null) {
482 throw new IllegalArgumentException(
483 "RepositoryJavaClient.getFiltered: handler is missing");
485 DocumentFilter docFilter = handler.getDocumentFilter();
486 if (docFilter == null) {
487 throw new IllegalArgumentException(
488 "RepositoryJavaClient.getFiltered: handler has no Filter specified");
490 String docType = ctx.getDocumentType();
491 if (docType == null) {
492 throw new DocumentNotFoundException(
493 "Unable to find DocumentType for service " + ctx.getServiceName());
495 String domain = ctx.getRepositoryDomainName();
496 if (domain == null) {
497 throw new DocumentNotFoundException(
498 "Unable to find Domain for service " + ctx.getServiceName());
500 RepositoryInstance repoSession = null;
502 handler.prepare(Action.GET_ALL);
503 repoSession = getRepositorySession();
504 DocumentModelList docList = null;
505 String query = buildNXQLQuery(docType, docFilter.getWhereClause(), domain );
507 // If we have limit and/or offset, then pass true to get totalSize
508 // in returned DocumentModelList.
509 if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
510 docList = repoSession.query(query, null,
511 docFilter.getPageSize(), docFilter.getOffset(), true);
513 docList = repoSession.query(query);
516 if (logger.isDebugEnabled()) {
517 logger.debug("Executed NXQL query: " + query.toString());
520 //set repoSession to handle the document
521 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
522 DocumentWrapper<DocumentModelList> wrapDoc = new DocumentWrapperImpl<DocumentModelList>(docList);
523 handler.handle(Action.GET_ALL, wrapDoc);
524 handler.complete(Action.GET_ALL, wrapDoc);
525 } catch (DocumentException de) {
527 } catch (Exception e) {
528 if (logger.isDebugEnabled()) {
529 logger.debug("Caught exception ", e);
531 throw new DocumentException(e);
533 if (repoSession != null) {
534 releaseRepositorySession(repoSession);
540 * update given document in the Nuxeo repository
542 * @param ctx service context under which this method is invoked
546 * should be used by the caller to provide and transform the
548 * @throws DocumentException
551 public void update(ServiceContext ctx, String id, DocumentHandler handler)
552 throws BadRequestException, DocumentNotFoundException,
554 if (handler == null) {
555 throw new IllegalArgumentException(
556 "RepositoryJavaClient.update: handler is missing");
558 RepositoryInstance repoSession = null;
560 handler.prepare(Action.UPDATE);
561 repoSession = getRepositorySession();
562 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
563 DocumentModel doc = null;
565 doc = repoSession.getDocument(docRef);
566 } catch (ClientException ce) {
567 String msg = "Could not find document to update with id=" + id;
568 logger.error(msg, ce);
569 throw new DocumentNotFoundException(msg, ce);
571 //set reposession to handle the document
572 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
573 DocumentWrapper<DocumentModel> wrapDoc = new DocumentWrapperImpl<DocumentModel>(doc);
574 handler.handle(Action.UPDATE, wrapDoc);
575 repoSession.saveDocument(doc);
577 handler.complete(Action.UPDATE, wrapDoc);
578 } catch (BadRequestException bre) {
580 } catch (DocumentException de) {
582 } catch (Exception e) {
583 if (logger.isDebugEnabled()) {
584 logger.debug("Caught exception ", e);
586 throw new DocumentException(e);
588 if (repoSession != null) {
589 releaseRepositorySession(repoSession);
595 * delete a document from the Nuxeo repository
596 * @param ctx service context under which this method is invoked
599 * @throws DocumentException
602 public void delete(ServiceContext ctx, String id) throws DocumentNotFoundException,
605 if (logger.isDebugEnabled()) {
606 logger.debug("deleting document with id=" + id);
608 RepositoryInstance repoSession = null;
610 repoSession = getRepositorySession();
611 DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id);
613 repoSession.removeDocument(docRef);
614 } catch (ClientException ce) {
615 String msg = "could not find document to delete with id=" + id;
616 logger.error(msg, ce);
617 throw new DocumentNotFoundException(msg, ce);
620 } catch (DocumentException de) {
622 } catch (Exception e) {
623 if (logger.isDebugEnabled()) {
624 logger.debug("Caught exception ", e);
626 throw new DocumentException(e);
628 if (repoSession != null) {
629 releaseRepositorySession(repoSession);
635 public String createWorkspace(String tenantDomain, String workspaceName) throws Exception {
636 RepositoryInstance repoSession = null;
637 String workspaceId = null;
639 repoSession = getRepositorySession();
640 DocumentRef docRef = new PathRef(
642 + "/" + "workspaces");
643 DocumentModel parent = repoSession.getDocument(docRef);
644 DocumentModel doc = repoSession.createDocumentModel(parent.getPathAsString(),
645 workspaceName, "Workspace");
646 doc.setPropertyValue("dc:title", workspaceName);
647 doc.setPropertyValue("dc:description", "A CollectionSpace workspace for "
649 doc = repoSession.createDocument(doc);
650 workspaceId = doc.getId();
652 if (logger.isDebugEnabled()) {
653 logger.debug("created workspace name=" + workspaceName
654 + " id=" + workspaceId);
656 } catch (Exception e) {
657 if (logger.isDebugEnabled()) {
658 logger.debug("createWorkspace caught exception ", e);
662 if (repoSession != null) {
663 releaseRepositorySession(repoSession);
670 public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception {
671 String workspaceId = null;
672 RepositoryInstance repoSession = null;
674 repoSession = getRepositorySession();
675 DocumentRef docRef = new PathRef(
678 + "/" + workspaceName);
679 DocumentModel workspace = repoSession.getDocument(docRef);
680 workspaceId = workspace.getId();
681 } catch (DocumentException de) {
683 } catch (Exception e) {
684 if (logger.isDebugEnabled()) {
685 logger.debug("Caught exception ", e);
687 throw new DocumentException(e);
689 if (repoSession != null) {
690 releaseRepositorySession(repoSession);
696 private final String buildNXQLQuery(String docType, String where, String domain ) {
697 StringBuilder query = new StringBuilder("SELECT * FROM ");
698 query.append(docType);
699 // TODO This is a slow method for tenant-filter
700 // We should make this a property that is indexed.
701 query.append(" WHERE ecm:path STARTSWITH '/" + domain + "'");
702 if ((null != where) && (where.length() > 0)) {
703 // Due to an apparent bug/issue in how Nuxeo translates the NXQL query string
704 // into SQL, we need to parenthesize our 'where' clause
705 query.append(" AND " + "(" + where +")" + "AND ecm:isProxy = 0");
707 return query.toString();
710 private RepositoryInstance getRepositorySession() throws Exception {
711 // FIXME: is it possible to reuse repository session?
712 // Authentication failures happen while trying to reuse the session
713 NuxeoClient client = NuxeoConnector.getInstance().getClient();
714 RepositoryInstance repoSession = client.openRepository();
715 if (logger.isDebugEnabled()) {
716 logger.debug("getRepository() repository root: " + repoSession.getRootDocument());
721 private void releaseRepositorySession(RepositoryInstance repoSession) {
723 NuxeoClient client = NuxeoConnector.getInstance().getClient();
725 client.releaseRepository(repoSession);
726 } catch (Exception e) {
727 logger.error("Could not close the repository session", e);
728 // no need to throw this service specific exception