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.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.List;
33 import javax.security.auth.login.LoginContext;
34 import javax.security.auth.login.LoginException;
36 import org.apache.commons.logging.Log;
37 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 {
107 RepositoryManager repoMgr = Framework.getService(RepositoryManager.class);
108 return repoMgr.getRepository(repositoryName).open();
109 } catch (Exception e) {
110 throw new ClientException(e);
114 protected void closeCoreSession(LoginContext loginContext,
115 CoreSession session) throws ClientException {
116 if (loginContext != null) {
118 loginContext.logout();
119 } catch (LoginException e) {
120 throw new ClientException(e);
123 if (session != null) {
124 CoreInstance.getInstance().close(session);
128 private static RelationManager getRelationManager() throws Exception {
129 return Framework.getService(RelationManager.class);
132 public List<DocumentModel> getQuotes(DocumentModel docModel)
133 throws ClientException {
134 RelationManager relationManager;
135 Map<String, Serializable> ctxMap = new HashMap<String, Serializable>();
136 ctxMap.put(ResourceAdapter.CORE_SESSION_ID_CONTEXT_KEY, docModel.getSessionId());
138 relationManager = getRelationManager();
139 } catch (Exception e) {
140 throw new ClientException(e);
142 Resource docResource = relationManager.getResource(
143 config.documentNamespace, docModel, ctxMap);
144 if (docResource == null) {
145 throw new ClientException(
146 "Could not adapt document model to relation resource ; "
147 + "check the service relation adapters configuration");
150 // FIXME AT: why no filter on the predicate?
151 Statement pattern = new StatementImpl(null, null, docResource);
152 List<Statement> statementList = relationManager.getStatements(
153 config.graphName, pattern);
154 // XXX AT: BBB for when repository name was not included in the resource
156 Resource oldDocResource = new QNameResourceImpl(
157 config.documentNamespace, docModel.getId());
158 Statement oldPattern = new StatementImpl(null, null, oldDocResource);
159 statementList.addAll(relationManager.getStatements(config.graphName,
162 List<DocumentModel> commentList = new ArrayList<DocumentModel>();
163 for (Statement stmt : statementList) {
164 QNameResourceImpl subject = (QNameResourceImpl) stmt.getSubject();
166 DocumentModel commentDocModel = null;
168 commentDocModel = (DocumentModel) relationManager.getResourceRepresentation(
169 config.commentNamespace, subject, ctxMap);
170 } catch (Exception e) {
171 log.error("failed to retrieve commentDocModel from relations");
173 if (commentDocModel == null) {
174 // XXX AT: maybe user cannot see the comment
175 log.warn("Could not adapt comment relation subject to a document "
176 + "model; check the service relation adapters configuration");
179 commentList.add(commentDocModel);
182 QuoteSorter sorter = new QuoteSorter(true);
183 Collections.sort(commentList, sorter);
188 public DocumentModel createQuote(DocumentModel docModel, String comment,
189 String author) throws ClientException {
190 LoginContext loginContext = null;
191 CoreSession session = null;
193 loginContext = Framework.login();
194 session = openCoreSession(docModel.getRepositoryName());
196 DocumentModel commentDM = session.createDocumentModel("Comment");
197 commentDM.setProperty("comment", "text", comment);
198 commentDM.setProperty("comment", "author", author);
199 commentDM.setProperty("comment", "creationDate",
200 Calendar.getInstance());
201 commentDM = internalCreateQuote(session, docModel, commentDM, null);
205 } catch (Exception e) {
206 throw new ClientException(e);
208 closeCoreSession(loginContext, session);
212 public DocumentModel createQuote(DocumentModel docModel, String quote)
213 throws ClientException {
214 String author = getCurrentUser(docModel);
215 return createQuote(docModel, quote, author);
219 * If the author property on comment is not set, retrieve the author name
222 * @param docModel The document model that holds the session id
223 * @param comment The comment to update
224 * @throws ClientException
226 private static String updateAuthor(DocumentModel docModel,
227 DocumentModel comment) throws ClientException {
228 // update the author if not set
229 String author = (String) comment.getProperty("comment", "author");
230 if (author == null) {
231 log.debug("deprecated use of createComment: the client should set the author property on document");
232 author = getCurrentUser(docModel);
233 comment.setProperty("comment", "author", author);
238 public DocumentModel createQuote(DocumentModel docModel,
239 DocumentModel comment) throws ClientException {
240 LoginContext loginContext = null;
241 CoreSession session = null;
243 loginContext = Framework.login();
244 session = openCoreSession(docModel.getRepositoryName());
245 DocumentModel doc = internalCreateQuote(session, docModel, comment, null);
248 } catch (Exception e) {
249 throw new ClientException(e);
251 closeCoreSession(loginContext, session);
255 protected DocumentModel internalCreateQuote(CoreSession session,
256 DocumentModel docModel, DocumentModel comment, String path)
257 throws ClientException {
258 String author = updateAuthor(docModel, comment);
259 DocumentModel createdComment;
262 createdComment = createQuoteDocModel(session, docModel, comment, path);
264 RelationManager relationManager = getRelationManager();
265 List<Statement> statementList = new ArrayList<Statement>();
267 Resource commentRes = relationManager.getResource(
268 config.commentNamespace, createdComment, null);
270 Resource documentRes = relationManager.getResource(
271 config.documentNamespace, docModel, null);
273 if (commentRes == null || documentRes == null) {
274 throw new ClientException(
275 "Could not adapt document model to relation resource ; "
276 + "check the service relation adapters configuration");
279 Resource predicateRes = new ResourceImpl(config.predicateNamespace);
281 Statement stmt = new StatementImpl(commentRes, predicateRes,
283 statementList.add(stmt);
284 relationManager.add(config.graphName, statementList);
285 } catch (Exception e) {
286 throw new ClientException("failed to create comment", e);
289 NuxeoPrincipal principal = null;
291 UserManager userManager = Framework.getService(UserManager.class);
292 principal = userManager.getPrincipal(author);
293 } catch (Exception e) {
294 log.error("Error building principal for notification", e);
296 notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, null,
297 createdComment, principal);
299 return createdComment;
302 private DocumentModel createQuoteDocModel(CoreSession mySession,
303 DocumentModel docModel, DocumentModel comment, String path)
304 throws ClientException {
307 updateAuthor(docModel, comment);
309 String[] pathList = getQuotePathList(comment);
312 domainPath = docModel.getPath().segment(0);
316 if (mySession == null) {
320 // TODO GR upgrade this code. It can't work if current user
321 // doesn't have admin rights
323 DocumentModel parent = mySession.getDocument(new PathRef(domainPath));
324 for (String name : pathList) {
325 boolean found = false;
326 String pathStr = parent.getPathAsString();
327 if (name.equals(COMMENTS_DIRECTORY)) {
328 List<DocumentModel> children = mySession.getChildren(new PathRef(pathStr),
330 for (DocumentModel documentModel : children) {
331 if (documentModel.getTitle().equals(COMMENTS_DIRECTORY)) {
333 parent = documentModel;
338 DocumentRef ref = new PathRef(pathStr, name);
339 if (mySession.exists(ref)) {
340 parent = mySession.getDocument(ref);
346 DocumentModel dm = mySession.createDocumentModel(pathStr, name,
348 dm.setProperty("dublincore", "title", name);
349 dm.setProperty("dublincore", "description", "");
350 dm.setProperty("dublincore", "created", Calendar.getInstance());
351 dm = mySession.createDocument(dm);
352 setFolderPermissions(dm);
357 String pathStr = parent.getPathAsString();
358 String commentName = getQuoteName(docModel, comment);
359 QuoteConverter converter = config.getQuoteConverter();
360 DocumentModel commentDocModel = mySession.createDocumentModel(pathStr,
361 IdUtils.generateId(commentName), comment.getType());
362 converter.updateDocumentModel(commentDocModel, comment);
363 commentDocModel.setProperty("dublincore", "title", commentName);
364 commentDocModel = mySession.createDocument(commentDocModel);
365 setQuotePermissions(commentDocModel);
366 log.debug("created comment with id=" + commentDocModel.getId());
368 return commentDocModel;
371 private static void notifyEvent(CoreSession session, DocumentModel docModel, String eventType,
372 DocumentModel parent, DocumentModel child, NuxeoPrincipal principal)
373 throws ClientException {
375 DocumentEventContext ctx = new DocumentEventContext(session, principal, docModel);
376 Map<String, Serializable> props = new HashMap<String, Serializable>();
377 if (parent != null) {
378 props.put(QuoteConstants.PARENT_COMMENT, parent);
380 props.put(QuoteConstants.COMMENT, child);
381 props.put(QuoteConstants.COMMENT_TEXT, (String) child.getProperty(
383 props.put("category", QuoteConstants.EVENT_COMMENT_CATEGORY);
384 ctx.setProperties(props);
385 Event event = ctx.newEvent(eventType);
388 EventProducer evtProducer = Framework.getService(EventProducer.class);
389 evtProducer.fireEvent(event);
390 } catch (Exception e) {
391 log.error("Error while send message", e);
393 // send also a synchronous Seam message so the CommentManagerActionBean
394 // can rebuild its list
395 // Events.instance().raiseEvent(eventType, docModel);
398 private static void setFolderPermissions(DocumentModel dm) {
399 ACP acp = new ACPImpl();
400 ACE grantAddChildren = new ACE("members",
401 SecurityConstants.ADD_CHILDREN, true);
402 ACE grantRemoveChildren = new ACE("members",
403 SecurityConstants.REMOVE_CHILDREN, true);
404 ACE grantRemove = new ACE("members", SecurityConstants.REMOVE, true);
405 ACL acl = new ACLImpl();
406 acl.setACEs(new ACE[] { grantAddChildren, grantRemoveChildren,
410 dm.setACP(acp, true);
411 } catch (ClientException e) {
412 throw new ClientRuntimeException(e);
416 private static void setQuotePermissions(DocumentModel dm) {
417 ACP acp = new ACPImpl();
418 ACE grantRead = new ACE(SecurityConstants.EVERYONE,
419 SecurityConstants.READ, true);
420 ACE grantRemove = new ACE("members", SecurityConstants.REMOVE, true);
421 ACL acl = new ACLImpl();
422 acl.setACEs(new ACE[] { grantRead, grantRemove });
425 dm.setACP(acp, true);
426 } catch (ClientException e) {
427 throw new ClientRuntimeException(e);
431 private String[] getQuotePathList(DocumentModel comment) {
432 String[] pathList = new String[2];
433 pathList[0] = COMMENTS_DIRECTORY;
435 pathList[1] = dateFormat.format(getQuoteTimeStamp(comment));
439 private static CoreSession getUserSession(String sid) {
440 return CoreInstance.getInstance().getSession(sid);
444 * @deprecated if the caller is remote, we cannot obtain the session
447 private static String getCurrentUser(DocumentModel target)
448 throws ClientException {
449 String sid = target.getSessionId();
450 CoreSession userSession = getUserSession(sid);
451 if (userSession == null) {
452 throw new ClientException(
453 "userSession is null, do not invoke this method when the user is not local");
455 return userSession.getPrincipal().getName();
458 private String getQuoteName(DocumentModel target, DocumentModel comment)
459 throws ClientException {
460 String author = (String) comment.getProperty("comment", "author");
461 if (author == null) {
462 author = getCurrentUser(target);
464 Date creationDate = getQuoteTimeStamp(comment);
465 return "COMMENT-" + author + '-'
466 + timeFormat.format(creationDate.getTime());
469 private static Date getQuoteTimeStamp(DocumentModel comment) {
470 Calendar creationDate;
472 creationDate = (Calendar) comment.getProperty("dublincore",
474 } catch (ClientException e) {
477 if (creationDate == null) {
478 creationDate = Calendar.getInstance();
480 return creationDate.getTime();
483 public void deleteQuote(DocumentModel docModel, DocumentModel comment)
484 throws ClientException {
485 LoginContext loginContext = null;
486 CoreSession session = null;
488 loginContext = Framework.login();
489 session = openCoreSession(docModel.getRepositoryName());
491 if (session == null) {
492 throw new ClientException(
493 "Unable to acess repository for comment: "
496 DocumentRef ref = comment.getRef();
497 if (!session.exists(ref)) {
498 throw new ClientException("Comment Document does not exist: "
502 NuxeoPrincipal author = getAuthor(comment);
503 session.removeDocument(ref);
505 notifyEvent(session, docModel, QuoteEvents.COMMENT_REMOVED, null, comment,
510 } catch (Throwable e) {
511 log.error("failed to delete comment", e);
512 throw new ClientException("failed to delete comment", e);
514 closeCoreSession(loginContext, session);
518 public DocumentModel createQuote(DocumentModel docModel,
519 DocumentModel parent, DocumentModel child) throws ClientException {
520 LoginContext loginContext = null;
521 CoreSession session = null;
523 loginContext = Framework.login();
524 session = openCoreSession(docModel.getRepositoryName());
526 String author = updateAuthor(docModel, child);
527 DocumentModel parentDocModel = session.getDocument(parent.getRef());
528 DocumentModel newComment = internalCreateQuote(session, parentDocModel,
531 UserManager userManager = Framework.getService(UserManager.class);
532 NuxeoPrincipal principal = userManager.getPrincipal(author);
533 notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, parent,
534 newComment, principal);
539 } catch (Exception e) {
540 throw new ClientException(e);
542 closeCoreSession(loginContext, session);
546 private static NuxeoPrincipal getAuthor(DocumentModel docModel) {
548 String[] contributors = (String[]) docModel.getProperty(
549 "dublincore", "contributors");
550 UserManager userManager = Framework.getService(UserManager.class);
551 return userManager.getPrincipal(contributors[0]);
552 } catch (Exception e) {
553 log.error("Error building principal for comment author", e);
558 public List<DocumentModel> getQuotes(DocumentModel docModel,
559 DocumentModel parent) throws ClientException {
561 //loginContext = Framework.login();
562 //session = openCoreSession(docModel.getRepositoryName());
563 //DocumentModel parentDocModel = session.getDocument(parent.getRef());
564 return getQuotes(parent);
565 } catch (Exception e) {
566 throw new ClientException(e);
570 public List<DocumentModel> getDocumentsForQuote(DocumentModel comment)
571 throws ClientException {
572 RelationManager relationManager;
573 Map<String, Serializable> ctxMap = new HashMap<String, Serializable>();
574 ctxMap.put(ResourceAdapter.CORE_SESSION_ID_CONTEXT_KEY, comment.getSessionId());
576 relationManager = getRelationManager();
577 } catch (Exception e) {
578 throw new ClientException(e);
580 Resource commentResource = relationManager.getResource(
581 config.commentNamespace, comment, ctxMap);
582 if (commentResource == null) {
583 throw new ClientException(
584 "Could not adapt document model to relation resource ; "
585 + "check the service relation adapters configuration");
587 Resource predicate = new ResourceImpl(config.predicateNamespace);
588 Statement pattern = new StatementImpl(commentResource, predicate, null);
590 List<Statement> statementList = relationManager.getStatements(
591 config.graphName, pattern);
592 // XXX AT: BBB for when repository name was not included in the resource
594 Resource oldDocResource = new QNameResourceImpl(
595 config.commentNamespace, comment.getId());
596 Statement oldPattern = new StatementImpl(oldDocResource, predicate, null);
597 statementList.addAll(relationManager.getStatements(config.graphName,
600 List<DocumentModel> docList = new ArrayList<DocumentModel>();
601 for (Statement stmt : statementList) {
602 QNameResourceImpl subject = (QNameResourceImpl) stmt.getObject();
603 DocumentModel docModel = null;
605 docModel = (DocumentModel) relationManager.getResourceRepresentation(
606 config.documentNamespace, subject, ctxMap);
607 } catch (Exception e) {
608 log.error("failed to retrieve documents from relations");
610 if (docModel == null) {
611 log.warn("Could not adapt comment relation subject to a document "
612 + "model; check the service relation adapters configuration");
615 docList.add(docModel);
620 public DocumentModel createLocatedQuote(DocumentModel docModel,
621 DocumentModel comment, String path) throws ClientException {
622 LoginContext loginContext = null;
623 CoreSession session = null;
624 DocumentModel createdComment;
626 loginContext = Framework.login();
627 session = openCoreSession(docModel.getRepositoryName());
628 createdComment = internalCreateQuote(session, docModel, comment, path);
630 } catch (Exception e) {
631 throw new ClientException(e);
633 closeCoreSession(loginContext, session);
636 return createdComment;