2 * (C) Copyright 2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Lesser General Public License
6 * (LGPL) version 2.1 which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/lgpl.html
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
15 * Nuxeo - initial API and implementation
20 package org.collectionspace.ecm.platform.quote.impl;
22 import java.io.Serializable;
23 import java.text.SimpleDateFormat;
24 import java.util.ArrayList;
25 import java.util.Calendar;
26 import java.util.Collections;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.List;
32 import javax.security.auth.login.LoginContext;
33 import javax.security.auth.login.LoginException;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
38 import org.nuxeo.common.utils.IdUtils;
39 import org.nuxeo.ecm.core.api.ClientException;
40 import org.nuxeo.ecm.core.api.ClientRuntimeException;
41 import org.nuxeo.ecm.core.api.CoreInstance;
42 import org.nuxeo.ecm.core.api.CoreSession;
43 import org.nuxeo.ecm.core.api.DocumentModel;
44 import org.nuxeo.ecm.core.api.DocumentRef;
45 import org.nuxeo.ecm.core.api.NuxeoPrincipal;
46 import org.nuxeo.ecm.core.api.PathRef;
47 import org.nuxeo.ecm.core.api.repository.RepositoryManager;
48 import org.nuxeo.ecm.core.api.security.ACE;
49 import org.nuxeo.ecm.core.api.security.ACL;
50 import org.nuxeo.ecm.core.api.security.ACP;
51 import org.nuxeo.ecm.core.api.security.SecurityConstants;
52 import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
53 import org.nuxeo.ecm.core.api.security.impl.ACPImpl;
54 import org.nuxeo.ecm.core.event.Event;
55 import org.nuxeo.ecm.core.event.EventProducer;
56 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
57 import org.nuxeo.ecm.platform.relations.api.RelationManager;
58 import org.nuxeo.ecm.platform.relations.api.Resource;
59 import org.nuxeo.ecm.platform.relations.api.ResourceAdapter;
60 import org.nuxeo.ecm.platform.relations.api.Statement;
61 import org.nuxeo.ecm.platform.relations.api.impl.QNameResourceImpl;
62 import org.nuxeo.ecm.platform.relations.api.impl.ResourceImpl;
63 import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl;
64 import org.nuxeo.ecm.platform.usermanager.UserManager;
65 import org.nuxeo.runtime.api.Framework;
67 import org.collectionspace.ecm.platform.quote.api.QuoteConstants;
68 import org.collectionspace.ecm.platform.quote.api.QuoteConverter;
69 import org.collectionspace.ecm.platform.quote.api.QuoteEvents;
70 import org.collectionspace.ecm.platform.quote.api.QuoteManager;
71 import org.collectionspace.ecm.platform.quote.service.QuoteServiceConfig;
74 * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a>
77 public class QuoteManagerImpl implements QuoteManager {
79 private static final Log log = LogFactory.getLog(QuoteManagerImpl.class);
81 final SimpleDateFormat timeFormat = new SimpleDateFormat("dd-HHmmss.S");
83 final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
85 final QuoteServiceConfig config;
87 final QuoteConverter quoteConverter;
89 public static final String COMMENTS_DIRECTORY = "Comments";
91 public QuoteManagerImpl(QuoteServiceConfig config) {
93 config = new QuoteServiceConfig();
94 config.quoteConverterClassName = QuoteConverterImpl.class.getName();
95 config.graphName = "documentQuotes";
96 config.commentNamespace = "http://www.collectionspace.org/quotes/uid";
97 config.documentNamespace = "http://www.collectionspace.org/document/uid";
98 config.predicateNamespace = "http://www.nuxeo.org/predicates/isQuoteFor";
100 this.config = config;
101 quoteConverter = config.getQuoteConverter();
104 protected CoreSession openCoreSession(String repositoryName)
105 throws ClientException {
106 CoreSession result = null;
109 result = CoreInstance.openCoreSession(repositoryName);
110 } catch (Exception e) {
111 throw new ClientException(e);
117 protected void closeCoreSession(LoginContext loginContext,
118 CoreSession session) throws ClientException {
119 if (loginContext != null) {
121 loginContext.logout();
122 } catch (LoginException e) {
123 throw new ClientException(e);
126 if (session != null) {
131 private static RelationManager getRelationManager() throws Exception {
132 return Framework.getService(RelationManager.class);
135 public List<DocumentModel> getQuotes(DocumentModel docModel)
136 throws ClientException {
137 RelationManager relationManager;
138 Map<String, Object> ctxMap = new HashMap<String, Object>();
139 ctxMap.put(ResourceAdapter.CORE_SESSION_CONTEXT_KEY, docModel.getSessionId());
141 relationManager = getRelationManager();
142 } catch (Exception e) {
143 throw new ClientException(e);
145 Resource docResource = relationManager.getResource(
146 config.documentNamespace, docModel, ctxMap);
147 if (docResource == null) {
148 throw new ClientException(
149 "Could not adapt document model to relation resource ; "
150 + "check the service relation adapters configuration");
153 // FIXME AT: why no filter on the predicate?
154 Statement pattern = new StatementImpl(null, null, docResource);
155 List<Statement> statementList = relationManager.getStatements(
156 config.graphName, pattern);
157 // XXX AT: BBB for when repository name was not included in the resource
159 Resource oldDocResource = new QNameResourceImpl(
160 config.documentNamespace, docModel.getId());
161 Statement oldPattern = new StatementImpl(null, null, oldDocResource);
162 statementList.addAll(relationManager.getStatements(config.graphName,
165 List<DocumentModel> commentList = new ArrayList<DocumentModel>();
166 for (Statement stmt : statementList) {
167 QNameResourceImpl subject = (QNameResourceImpl) stmt.getSubject();
169 DocumentModel commentDocModel = null;
171 commentDocModel = (DocumentModel) relationManager.getResourceRepresentation(
172 config.commentNamespace, subject, ctxMap);
173 } catch (Exception e) {
174 log.error("failed to retrieve commentDocModel from relations");
176 if (commentDocModel == null) {
177 // XXX AT: maybe user cannot see the comment
178 log.warn("Could not adapt comment relation subject to a document "
179 + "model; check the service relation adapters configuration");
182 commentList.add(commentDocModel);
185 QuoteSorter sorter = new QuoteSorter(true);
186 Collections.sort(commentList, sorter);
191 public DocumentModel createQuote(DocumentModel docModel, String comment,
192 String author) throws ClientException {
193 LoginContext loginContext = null;
194 CoreSession session = null;
196 loginContext = Framework.login();
197 session = openCoreSession(docModel.getRepositoryName());
199 DocumentModel commentDM = session.createDocumentModel("Comment");
200 commentDM.setProperty("comment", "text", comment);
201 commentDM.setProperty("comment", "author", author);
202 commentDM.setProperty("comment", "creationDate",
203 Calendar.getInstance());
204 commentDM = internalCreateQuote(session, docModel, commentDM, null);
208 } catch (Exception e) {
209 throw new ClientException(e);
211 closeCoreSession(loginContext, session);
215 public DocumentModel createQuote(DocumentModel docModel, String quote)
216 throws ClientException {
217 String author = getCurrentUser(docModel);
218 return createQuote(docModel, quote, author);
222 * If the author property on comment is not set, retrieve the author name
225 * @param docModel The document model that holds the session id
226 * @param comment The comment to update
227 * @throws ClientException
229 private static String updateAuthor(DocumentModel docModel,
230 DocumentModel comment) throws ClientException {
231 // update the author if not set
232 String author = (String) comment.getProperty("comment", "author");
233 if (author == null) {
234 log.debug("deprecated use of createComment: the client should set the author property on document");
235 author = getCurrentUser(docModel);
236 comment.setProperty("comment", "author", author);
241 public DocumentModel createQuote(DocumentModel docModel,
242 DocumentModel comment) throws ClientException {
243 LoginContext loginContext = null;
244 CoreSession session = null;
246 loginContext = Framework.login();
247 session = openCoreSession(docModel.getRepositoryName());
248 DocumentModel doc = internalCreateQuote(session, docModel, comment, null);
251 } catch (Exception e) {
252 throw new ClientException(e);
254 closeCoreSession(loginContext, session);
258 protected DocumentModel internalCreateQuote(CoreSession session,
259 DocumentModel docModel, DocumentModel comment, String path)
260 throws ClientException {
261 String author = updateAuthor(docModel, comment);
262 DocumentModel createdComment;
265 createdComment = createQuoteDocModel(session, docModel, comment, path);
267 RelationManager relationManager = getRelationManager();
268 List<Statement> statementList = new ArrayList<Statement>();
270 Resource commentRes = relationManager.getResource(
271 config.commentNamespace, createdComment, null);
273 Resource documentRes = relationManager.getResource(
274 config.documentNamespace, docModel, null);
276 if (commentRes == null || documentRes == null) {
277 throw new ClientException(
278 "Could not adapt document model to relation resource ; "
279 + "check the service relation adapters configuration");
282 Resource predicateRes = new ResourceImpl(config.predicateNamespace);
284 Statement stmt = new StatementImpl(commentRes, predicateRes,
286 statementList.add(stmt);
287 relationManager.add(config.graphName, statementList);
288 } catch (Exception e) {
289 throw new ClientException("failed to create comment", e);
292 NuxeoPrincipal principal = null;
294 UserManager userManager = Framework.getService(UserManager.class);
295 principal = userManager.getPrincipal(author);
296 } catch (Exception e) {
297 log.error("Error building principal for notification", e);
299 notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, null,
300 createdComment, principal);
302 return createdComment;
305 private DocumentModel createQuoteDocModel(CoreSession mySession,
306 DocumentModel docModel, DocumentModel comment, String path)
307 throws ClientException {
310 updateAuthor(docModel, comment);
312 String[] pathList = getQuotePathList(comment);
315 domainPath = docModel.getPath().segment(0);
319 if (mySession == null) {
323 // TODO GR upgrade this code. It can't work if current user
324 // doesn't have admin rights
326 DocumentModel parent = mySession.getDocument(new PathRef(domainPath));
327 for (String name : pathList) {
328 boolean found = false;
329 String pathStr = parent.getPathAsString();
330 if (name.equals(COMMENTS_DIRECTORY)) {
331 List<DocumentModel> children = mySession.getChildren(new PathRef(pathStr),
333 for (DocumentModel documentModel : children) {
334 if (documentModel.getTitle().equals(COMMENTS_DIRECTORY)) {
336 parent = documentModel;
341 DocumentRef ref = new PathRef(pathStr, name);
342 if (mySession.exists(ref)) {
343 parent = mySession.getDocument(ref);
349 DocumentModel dm = mySession.createDocumentModel(pathStr, name,
351 dm.setProperty("dublincore", "title", name);
352 dm.setProperty("dublincore", "description", "");
353 dm.setProperty("dublincore", "created", Calendar.getInstance());
354 dm = mySession.createDocument(dm);
355 setFolderPermissions(dm);
360 String pathStr = parent.getPathAsString();
361 String commentName = getQuoteName(docModel, comment);
362 QuoteConverter converter = config.getQuoteConverter();
363 DocumentModel commentDocModel = mySession.createDocumentModel(pathStr,
364 IdUtils.generateId(commentName), comment.getType());
365 converter.updateDocumentModel(commentDocModel, comment);
366 commentDocModel.setProperty("dublincore", "title", commentName);
367 commentDocModel = mySession.createDocument(commentDocModel);
368 setQuotePermissions(commentDocModel);
369 log.debug("created comment with id=" + commentDocModel.getId());
371 return commentDocModel;
374 private static void notifyEvent(CoreSession session, DocumentModel docModel, String eventType,
375 DocumentModel parent, DocumentModel child, NuxeoPrincipal principal)
376 throws ClientException {
378 DocumentEventContext ctx = new DocumentEventContext(session, principal, docModel);
379 Map<String, Serializable> props = new HashMap<String, Serializable>();
380 if (parent != null) {
381 props.put(QuoteConstants.PARENT_COMMENT, parent);
383 props.put(QuoteConstants.COMMENT, child);
384 props.put(QuoteConstants.COMMENT_TEXT, (String) child.getProperty(
386 props.put("category", QuoteConstants.EVENT_COMMENT_CATEGORY);
387 ctx.setProperties(props);
388 Event event = ctx.newEvent(eventType);
391 EventProducer evtProducer = Framework.getService(EventProducer.class);
392 evtProducer.fireEvent(event);
393 } catch (Exception e) {
394 log.error("Error while send message", e);
396 // send also a synchronous Seam message so the CommentManagerActionBean
397 // can rebuild its list
398 // Events.instance().raiseEvent(eventType, docModel);
401 private static void setFolderPermissions(DocumentModel dm) {
402 ACP acp = new ACPImpl();
403 ACE grantAddChildren = new ACE("members",
404 SecurityConstants.ADD_CHILDREN, true);
405 ACE grantRemoveChildren = new ACE("members",
406 SecurityConstants.REMOVE_CHILDREN, true);
407 ACE grantRemove = new ACE("members", SecurityConstants.REMOVE, true);
408 ACL acl = new ACLImpl();
409 acl.setACEs(new ACE[] { grantAddChildren, grantRemoveChildren,
413 dm.setACP(acp, true);
414 } catch (ClientException e) {
415 throw new ClientRuntimeException(e);
419 private static void setQuotePermissions(DocumentModel dm) {
420 ACP acp = new ACPImpl();
421 ACE grantRead = new ACE(SecurityConstants.EVERYONE,
422 SecurityConstants.READ, true);
423 ACE grantRemove = new ACE("members", SecurityConstants.REMOVE, true);
424 ACL acl = new ACLImpl();
425 acl.setACEs(new ACE[] { grantRead, grantRemove });
428 dm.setACP(acp, true);
429 } catch (ClientException e) {
430 throw new ClientRuntimeException(e);
434 private String[] getQuotePathList(DocumentModel comment) {
435 String[] pathList = new String[2];
436 pathList[0] = COMMENTS_DIRECTORY;
438 pathList[1] = dateFormat.format(getQuoteTimeStamp(comment));
442 private static CoreSession getUserSession(String sid) {
443 return CoreInstance.getInstance().getSession(sid);
447 * @deprecated if the caller is remote, we cannot obtain the session
450 private static String getCurrentUser(DocumentModel target)
451 throws ClientException {
452 String sid = target.getSessionId();
453 CoreSession userSession = getUserSession(sid);
454 if (userSession == null) {
455 throw new ClientException(
456 "userSession is null, do not invoke this method when the user is not local");
458 return userSession.getPrincipal().getName();
461 private String getQuoteName(DocumentModel target, DocumentModel comment)
462 throws ClientException {
463 String author = (String) comment.getProperty("comment", "author");
464 if (author == null) {
465 author = getCurrentUser(target);
467 Date creationDate = getQuoteTimeStamp(comment);
468 return "COMMENT-" + author + '-'
469 + timeFormat.format(creationDate.getTime());
472 private static Date getQuoteTimeStamp(DocumentModel comment) {
473 Calendar creationDate;
475 creationDate = (Calendar) comment.getProperty("dublincore",
477 } catch (ClientException e) {
480 if (creationDate == null) {
481 creationDate = Calendar.getInstance();
483 return creationDate.getTime();
486 public void deleteQuote(DocumentModel docModel, DocumentModel comment)
487 throws ClientException {
488 LoginContext loginContext = null;
489 CoreSession session = null;
491 loginContext = Framework.login();
492 session = openCoreSession(docModel.getRepositoryName());
494 if (session == null) {
495 throw new ClientException(
496 "Unable to acess repository for comment: "
499 DocumentRef ref = comment.getRef();
500 if (!session.exists(ref)) {
501 throw new ClientException("Comment Document does not exist: "
505 NuxeoPrincipal author = getAuthor(comment);
506 session.removeDocument(ref);
508 notifyEvent(session, docModel, QuoteEvents.COMMENT_REMOVED, null, comment,
513 } catch (Throwable e) {
514 log.error("failed to delete comment", e);
515 throw new ClientException("failed to delete comment", e);
517 closeCoreSession(loginContext, session);
521 public DocumentModel createQuote(DocumentModel docModel,
522 DocumentModel parent, DocumentModel child) throws ClientException {
523 LoginContext loginContext = null;
524 CoreSession session = null;
526 loginContext = Framework.login();
527 session = openCoreSession(docModel.getRepositoryName());
529 String author = updateAuthor(docModel, child);
530 DocumentModel parentDocModel = session.getDocument(parent.getRef());
531 DocumentModel newComment = internalCreateQuote(session, parentDocModel,
534 UserManager userManager = Framework.getService(UserManager.class);
535 NuxeoPrincipal principal = userManager.getPrincipal(author);
536 notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, parent,
537 newComment, principal);
542 } catch (Exception e) {
543 throw new ClientException(e);
545 closeCoreSession(loginContext, session);
549 private static NuxeoPrincipal getAuthor(DocumentModel docModel) {
551 String[] contributors = (String[]) docModel.getProperty(
552 "dublincore", "contributors");
553 UserManager userManager = Framework.getService(UserManager.class);
554 return userManager.getPrincipal(contributors[0]);
555 } catch (Exception e) {
556 log.error("Error building principal for comment author", e);
561 public List<DocumentModel> getQuotes(DocumentModel docModel,
562 DocumentModel parent) throws ClientException {
564 //loginContext = Framework.login();
565 //session = openCoreSession(docModel.getRepositoryName());
566 //DocumentModel parentDocModel = session.getDocument(parent.getRef());
567 return getQuotes(parent);
568 } catch (Exception e) {
569 throw new ClientException(e);
573 public List<DocumentModel> getDocumentsForQuote(DocumentModel comment)
574 throws ClientException {
575 RelationManager relationManager;
576 Map<String, Object> ctxMap = new HashMap<String, Object>();
577 ctxMap.put(ResourceAdapter.CORE_SESSION_CONTEXT_KEY, comment.getSessionId());
579 relationManager = getRelationManager();
580 } catch (Exception e) {
581 throw new ClientException(e);
583 Resource commentResource = relationManager.getResource(
584 config.commentNamespace, comment, ctxMap);
585 if (commentResource == null) {
586 throw new ClientException(
587 "Could not adapt document model to relation resource ; "
588 + "check the service relation adapters configuration");
590 Resource predicate = new ResourceImpl(config.predicateNamespace);
591 Statement pattern = new StatementImpl(commentResource, predicate, null);
593 List<Statement> statementList = relationManager.getStatements(
594 config.graphName, pattern);
595 // XXX AT: BBB for when repository name was not included in the resource
597 Resource oldDocResource = new QNameResourceImpl(
598 config.commentNamespace, comment.getId());
599 Statement oldPattern = new StatementImpl(oldDocResource, predicate, null);
600 statementList.addAll(relationManager.getStatements(config.graphName,
603 List<DocumentModel> docList = new ArrayList<DocumentModel>();
604 for (Statement stmt : statementList) {
605 QNameResourceImpl subject = (QNameResourceImpl) stmt.getObject();
606 DocumentModel docModel = null;
608 docModel = (DocumentModel) relationManager.getResourceRepresentation(
609 config.documentNamespace, subject, ctxMap);
610 } catch (Exception e) {
611 log.error("failed to retrieve documents from relations");
613 if (docModel == null) {
614 log.warn("Could not adapt comment relation subject to a document "
615 + "model; check the service relation adapters configuration");
618 docList.add(docModel);
623 public DocumentModel createLocatedQuote(DocumentModel docModel,
624 DocumentModel comment, String path) throws ClientException {
625 LoginContext loginContext = null;
626 CoreSession session = null;
627 DocumentModel createdComment;
629 loginContext = Framework.login();
630 session = openCoreSession(docModel.getRepositoryName());
631 createdComment = internalCreateQuote(session, docModel, comment, path);
633 } catch (Exception e) {
634 throw new ClientException(e);
636 closeCoreSession(loginContext, session);
639 return createdComment;