]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
91a02d0d7f5e03899ea118ec32239149f4e63db6
[tmp/jakarta-migration.git] /
1 /*
2  * (C) Copyright 2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
3  *
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
8  *
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.
13  *
14  * Contributors:
15  *     Nuxeo - initial API and implementation
16  *
17  * $Id$
18  */
19
20 package org.collectionspace.ecm.platform.quote.impl;
21
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;
30 import java.util.Map;
31
32 import javax.security.auth.login.LoginContext;
33 import javax.security.auth.login.LoginException;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37
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;
66
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;
72
73 /**
74  * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a>
75  *
76  */
77 public class QuoteManagerImpl implements QuoteManager {
78
79     private static final Log log = LogFactory.getLog(QuoteManagerImpl.class);
80
81     final SimpleDateFormat timeFormat = new SimpleDateFormat("dd-HHmmss.S");
82
83     final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
84
85     final QuoteServiceConfig config;
86
87     final QuoteConverter quoteConverter;
88
89     public static final  String COMMENTS_DIRECTORY = "Comments";
90
91     public QuoteManagerImpl(QuoteServiceConfig config) {
92         if (config == null) {
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";
99         }
100         this.config = config;
101         quoteConverter = config.getQuoteConverter();
102     }
103
104     protected CoreSession openCoreSession(String repositoryName)
105             throws ClientException {
106         CoreSession result = null;
107         
108         try {
109                 result = CoreInstance.openCoreSession(repositoryName);
110         } catch (Exception e) {
111             throw new ClientException(e);
112         }
113         
114         return result;
115     }
116
117     protected void closeCoreSession(LoginContext loginContext,
118                 CoreSession session) throws ClientException {
119         if (loginContext != null) {
120             try {
121                 loginContext.logout();
122             } catch (LoginException e) {
123                 throw new ClientException(e);
124             }
125         }
126         if (session != null) {
127                 session.close();
128         }
129     }
130
131     private static RelationManager getRelationManager() throws Exception {
132         return Framework.getService(RelationManager.class);
133     }
134
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());
140         try {
141             relationManager = getRelationManager();
142         } catch (Exception e) {
143             throw new ClientException(e);
144         }
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");
151         }
152
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
158         // uri
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,
163                 oldPattern));
164
165         List<DocumentModel> commentList = new ArrayList<DocumentModel>();
166         for (Statement stmt : statementList) {
167             QNameResourceImpl subject = (QNameResourceImpl) stmt.getSubject();
168
169             DocumentModel commentDocModel = null;
170             try {
171                 commentDocModel = (DocumentModel) relationManager.getResourceRepresentation(
172                         config.commentNamespace, subject, ctxMap);
173             } catch (Exception e) {
174                 log.error("failed to retrieve commentDocModel from relations");
175             }
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");
180                 continue;
181             }
182             commentList.add(commentDocModel);
183         }
184
185         QuoteSorter sorter = new QuoteSorter(true);
186         Collections.sort(commentList, sorter);
187
188         return commentList;
189     }
190
191     public DocumentModel createQuote(DocumentModel docModel, String comment,
192             String author) throws ClientException {
193         LoginContext loginContext = null;
194         CoreSession session = null;
195         try {
196             loginContext = Framework.login();
197             session = openCoreSession(docModel.getRepositoryName());
198
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);
205             session.save();
206
207             return commentDM;
208         } catch (Exception e) {
209             throw new ClientException(e);
210         } finally {
211             closeCoreSession(loginContext, session);
212         }
213     }
214
215     public DocumentModel createQuote(DocumentModel docModel, String quote)
216             throws ClientException {
217         String author = getCurrentUser(docModel);
218         return createQuote(docModel, quote, author);
219     }
220
221     /**
222      * If the author property on comment is not set, retrieve the author name
223      * from the session
224      *
225      * @param docModel The document model that holds the session id
226      * @param comment The comment to update
227      * @throws ClientException
228      */
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);
237         }
238         return author;
239     }
240
241     public DocumentModel createQuote(DocumentModel docModel,
242             DocumentModel comment) throws ClientException {
243         LoginContext loginContext = null;
244         CoreSession session = null;
245         try {
246             loginContext = Framework.login();
247             session = openCoreSession(docModel.getRepositoryName());
248             DocumentModel doc = internalCreateQuote(session, docModel, comment, null);
249             session.save();
250             return doc;
251         } catch (Exception e) {
252             throw new ClientException(e);
253         } finally {
254             closeCoreSession(loginContext, session);
255         }
256     }
257
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;
263
264         try {
265             createdComment = createQuoteDocModel(session, docModel, comment, path);
266
267             RelationManager relationManager = getRelationManager();
268             List<Statement> statementList = new ArrayList<Statement>();
269
270             Resource commentRes = relationManager.getResource(
271                     config.commentNamespace, createdComment, null);
272
273             Resource documentRes = relationManager.getResource(
274                     config.documentNamespace, docModel, null);
275
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");
280             }
281
282             Resource predicateRes = new ResourceImpl(config.predicateNamespace);
283
284             Statement stmt = new StatementImpl(commentRes, predicateRes,
285                     documentRes);
286             statementList.add(stmt);
287             relationManager.add(config.graphName, statementList);
288         } catch (Exception e) {
289             throw new ClientException("failed to create comment", e);
290         }
291
292         NuxeoPrincipal principal = null;
293         try {
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);
298         }
299         notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, null,
300                 createdComment, principal);
301
302         return createdComment;
303     }
304
305     private DocumentModel createQuoteDocModel(CoreSession mySession,
306             DocumentModel docModel, DocumentModel comment, String path)
307             throws ClientException {
308
309         String domainPath;
310         updateAuthor(docModel, comment);
311
312         String[] pathList = getQuotePathList(comment);
313
314         if (path == null) {
315             domainPath = docModel.getPath().segment(0);
316         } else {
317             domainPath = path;
318         }
319         if (mySession == null) {
320             return null;
321         }
322
323         // TODO GR upgrade this code. It can't work if current user
324         // doesn't have admin rights
325
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),
332                         "HiddenFolder");
333                 for (DocumentModel documentModel : children) {
334                     if (documentModel.getTitle().equals(COMMENTS_DIRECTORY)) {
335                         found = true;
336                         parent = documentModel;
337                         break;
338                     }
339                 }
340             } else {
341                 DocumentRef ref = new PathRef(pathStr, name);
342                 if (mySession.exists(ref)) {
343                     parent = mySession.getDocument(ref);
344                     found = true;
345                 }
346
347             }
348             if (!found) {
349                 DocumentModel dm = mySession.createDocumentModel(pathStr, name,
350                         "HiddenFolder");
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);
356                 parent = dm;
357             }
358         }
359
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());
370
371         return commentDocModel;
372     }
373
374     private static void notifyEvent(CoreSession session, DocumentModel docModel, String eventType,
375             DocumentModel parent, DocumentModel child, NuxeoPrincipal principal)
376             throws ClientException {
377
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);
382         }
383         props.put(QuoteConstants.COMMENT, child);
384         props.put(QuoteConstants.COMMENT_TEXT, (String) child.getProperty(
385                 "comment", "text"));
386         props.put("category", QuoteConstants.EVENT_COMMENT_CATEGORY);
387         ctx.setProperties(props);
388         Event event = ctx.newEvent(eventType);
389
390         try {
391             EventProducer evtProducer = Framework.getService(EventProducer.class);
392             evtProducer.fireEvent(event);
393         } catch (Exception e) {
394             log.error("Error while send message", e);
395         }
396         // send also a synchronous Seam message so the CommentManagerActionBean
397         // can rebuild its list
398         // Events.instance().raiseEvent(eventType, docModel);
399     }
400
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,
410                 grantRemove });
411         acp.addACL(acl);
412         try {
413             dm.setACP(acp, true);
414         } catch (ClientException e) {
415             throw new ClientRuntimeException(e);
416         }
417     }
418
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 });
426         acp.addACL(acl);
427         try {
428             dm.setACP(acp, true);
429         } catch (ClientException e) {
430             throw new ClientRuntimeException(e);
431         }
432     }
433
434     private String[] getQuotePathList(DocumentModel comment) {
435         String[] pathList = new String[2];
436         pathList[0] = COMMENTS_DIRECTORY;
437
438         pathList[1] = dateFormat.format(getQuoteTimeStamp(comment));
439         return pathList;
440     }
441
442     private static CoreSession getUserSession(String sid) {
443         return CoreInstance.getInstance().getSession(sid);
444     }
445
446     /**
447      * @deprecated if the caller is remote, we cannot obtain the session
448      */
449     @Deprecated
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");
457         }
458         return userSession.getPrincipal().getName();
459     }
460
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);
466         }
467         Date creationDate = getQuoteTimeStamp(comment);
468         return "COMMENT-" + author + '-'
469                 + timeFormat.format(creationDate.getTime());
470     }
471
472     private static Date getQuoteTimeStamp(DocumentModel comment) {
473         Calendar creationDate;
474         try {
475             creationDate = (Calendar) comment.getProperty("dublincore",
476                     "created");
477         } catch (ClientException e) {
478             creationDate = null;
479         }
480         if (creationDate == null) {
481             creationDate = Calendar.getInstance();
482         }
483         return creationDate.getTime();
484     }
485
486     public void deleteQuote(DocumentModel docModel, DocumentModel comment)
487             throws ClientException {
488         LoginContext loginContext = null;
489         CoreSession session = null;
490         try {
491             loginContext = Framework.login();
492             session = openCoreSession(docModel.getRepositoryName());
493
494             if (session == null) {
495                 throw new ClientException(
496                         "Unable to acess repository for comment: "
497                                 + comment.getId());
498             }
499             DocumentRef ref = comment.getRef();
500             if (!session.exists(ref)) {
501                 throw new ClientException("Comment Document does not exist: "
502                         + comment.getId());
503             }
504
505             NuxeoPrincipal author = getAuthor(comment);
506             session.removeDocument(ref);
507
508             notifyEvent(session, docModel, QuoteEvents.COMMENT_REMOVED, null, comment,
509                     author);
510
511             session.save();
512
513         } catch (Throwable e) {
514             log.error("failed to delete comment", e);
515             throw new ClientException("failed to delete comment", e);
516         } finally {
517             closeCoreSession(loginContext, session);
518         }
519     }
520
521     public DocumentModel createQuote(DocumentModel docModel,
522             DocumentModel parent, DocumentModel child) throws ClientException {
523         LoginContext loginContext = null;
524         CoreSession session = null;
525         try {
526             loginContext = Framework.login();
527             session = openCoreSession(docModel.getRepositoryName());
528
529             String author = updateAuthor(docModel, child);
530             DocumentModel parentDocModel = session.getDocument(parent.getRef());
531             DocumentModel newComment = internalCreateQuote(session, parentDocModel,
532                     child, null);
533
534             UserManager userManager = Framework.getService(UserManager.class);
535             NuxeoPrincipal principal = userManager.getPrincipal(author);
536             notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, parent,
537                     newComment, principal);
538
539             session.save();
540             return newComment;
541
542         } catch (Exception e) {
543             throw new ClientException(e);
544         } finally {
545             closeCoreSession(loginContext, session);
546         }
547     }
548
549     private static NuxeoPrincipal getAuthor(DocumentModel docModel) {
550         try {
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);
557             return null;
558         }
559     }
560
561     public List<DocumentModel> getQuotes(DocumentModel docModel,
562             DocumentModel parent) throws ClientException {
563         try {
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);
570         }
571     }
572
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());
578         try {
579             relationManager = getRelationManager();
580         } catch (Exception e) {
581             throw new ClientException(e);
582         }
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");
589         }
590         Resource predicate = new ResourceImpl(config.predicateNamespace);
591         Statement pattern = new StatementImpl(commentResource, predicate, null);
592
593         List<Statement> statementList = relationManager.getStatements(
594                 config.graphName, pattern);
595         // XXX AT: BBB for when repository name was not included in the resource
596         // uri
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,
601                 oldPattern));
602
603         List<DocumentModel> docList = new ArrayList<DocumentModel>();
604         for (Statement stmt : statementList) {
605             QNameResourceImpl subject = (QNameResourceImpl) stmt.getObject();
606             DocumentModel docModel = null;
607             try {
608                 docModel = (DocumentModel) relationManager.getResourceRepresentation(
609                         config.documentNamespace, subject, ctxMap);
610             } catch (Exception e) {
611                 log.error("failed to retrieve documents from relations");
612             }
613             if (docModel == null) {
614                 log.warn("Could not adapt comment relation subject to a document "
615                         + "model; check the service relation adapters configuration");
616                 continue;
617             }
618             docList.add(docModel);
619         }
620         return docList;
621
622     }
623     public DocumentModel createLocatedQuote(DocumentModel docModel,
624             DocumentModel comment, String path) throws ClientException {
625         LoginContext loginContext = null;
626         CoreSession session = null;
627         DocumentModel createdComment;
628         try {
629             loginContext = Framework.login();
630             session = openCoreSession(docModel.getRepositoryName());
631             createdComment = internalCreateQuote(session, docModel, comment, path);
632             session.save();
633         } catch (Exception e) {
634             throw new ClientException(e);
635         } finally {
636             closeCoreSession(loginContext, session);
637         }
638
639         return createdComment;
640     }
641
642 }