]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
4deed9bc1699c5973f6feee0466ce6f2125da4ac
[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.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import javax.security.auth.login.LoginContext;
34 import javax.security.auth.login.LoginException;
35
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;
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         try {
107             RepositoryManager repoMgr = Framework.getService(RepositoryManager.class);
108             return repoMgr.getRepository(repositoryName).open();
109         } catch (Exception e) {
110             throw new ClientException(e);
111         }
112     }
113
114     protected void closeCoreSession(LoginContext loginContext,
115             CoreSession session) throws ClientException {
116         if (loginContext != null) {
117             try {
118                 loginContext.logout();
119             } catch (LoginException e) {
120                 throw new ClientException(e);
121             }
122         }
123         if (session != null) {
124             CoreInstance.getInstance().close(session);
125         }
126     }
127
128     private static RelationManager getRelationManager() throws Exception {
129         return Framework.getService(RelationManager.class);
130     }
131
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());
137         try {
138             relationManager = getRelationManager();
139         } catch (Exception e) {
140             throw new ClientException(e);
141         }
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");
148         }
149
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
155         // uri
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,
160                 oldPattern));
161
162         List<DocumentModel> commentList = new ArrayList<DocumentModel>();
163         for (Statement stmt : statementList) {
164             QNameResourceImpl subject = (QNameResourceImpl) stmt.getSubject();
165
166             DocumentModel commentDocModel = null;
167             try {
168                 commentDocModel = (DocumentModel) relationManager.getResourceRepresentation(
169                         config.commentNamespace, subject, ctxMap);
170             } catch (Exception e) {
171                 log.error("failed to retrieve commentDocModel from relations");
172             }
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");
177                 continue;
178             }
179             commentList.add(commentDocModel);
180         }
181
182         QuoteSorter sorter = new QuoteSorter(true);
183         Collections.sort(commentList, sorter);
184
185         return commentList;
186     }
187
188     public DocumentModel createQuote(DocumentModel docModel, String comment,
189             String author) throws ClientException {
190         LoginContext loginContext = null;
191         CoreSession session = null;
192         try {
193             loginContext = Framework.login();
194             session = openCoreSession(docModel.getRepositoryName());
195
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);
202             session.save();
203
204             return commentDM;
205         } catch (Exception e) {
206             throw new ClientException(e);
207         } finally {
208             closeCoreSession(loginContext, session);
209         }
210     }
211
212     public DocumentModel createQuote(DocumentModel docModel, String quote)
213             throws ClientException {
214         String author = getCurrentUser(docModel);
215         return createQuote(docModel, quote, author);
216     }
217
218     /**
219      * If the author property on comment is not set, retrieve the author name
220      * from the session
221      *
222      * @param docModel The document model that holds the session id
223      * @param comment The comment to update
224      * @throws ClientException
225      */
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);
234         }
235         return author;
236     }
237
238     public DocumentModel createQuote(DocumentModel docModel,
239             DocumentModel comment) throws ClientException {
240         LoginContext loginContext = null;
241         CoreSession session = null;
242         try {
243             loginContext = Framework.login();
244             session = openCoreSession(docModel.getRepositoryName());
245             DocumentModel doc = internalCreateQuote(session, docModel, comment, null);
246             session.save();
247             return doc;
248         } catch (Exception e) {
249             throw new ClientException(e);
250         } finally {
251             closeCoreSession(loginContext, session);
252         }
253     }
254
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;
260
261         try {
262             createdComment = createQuoteDocModel(session, docModel, comment, path);
263
264             RelationManager relationManager = getRelationManager();
265             List<Statement> statementList = new ArrayList<Statement>();
266
267             Resource commentRes = relationManager.getResource(
268                     config.commentNamespace, createdComment, null);
269
270             Resource documentRes = relationManager.getResource(
271                     config.documentNamespace, docModel, null);
272
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");
277             }
278
279             Resource predicateRes = new ResourceImpl(config.predicateNamespace);
280
281             Statement stmt = new StatementImpl(commentRes, predicateRes,
282                     documentRes);
283             statementList.add(stmt);
284             relationManager.add(config.graphName, statementList);
285         } catch (Exception e) {
286             throw new ClientException("failed to create comment", e);
287         }
288
289         NuxeoPrincipal principal = null;
290         try {
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);
295         }
296         notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, null,
297                 createdComment, principal);
298
299         return createdComment;
300     }
301
302     private DocumentModel createQuoteDocModel(CoreSession mySession,
303             DocumentModel docModel, DocumentModel comment, String path)
304             throws ClientException {
305
306         String domainPath;
307         updateAuthor(docModel, comment);
308
309         String[] pathList = getQuotePathList(comment);
310
311         if (path == null) {
312             domainPath = docModel.getPath().segment(0);
313         } else {
314             domainPath = path;
315         }
316         if (mySession == null) {
317             return null;
318         }
319
320         // TODO GR upgrade this code. It can't work if current user
321         // doesn't have admin rights
322
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),
329                         "HiddenFolder");
330                 for (DocumentModel documentModel : children) {
331                     if (documentModel.getTitle().equals(COMMENTS_DIRECTORY)) {
332                         found = true;
333                         parent = documentModel;
334                         break;
335                     }
336                 }
337             } else {
338                 DocumentRef ref = new PathRef(pathStr, name);
339                 if (mySession.exists(ref)) {
340                     parent = mySession.getDocument(ref);
341                     found = true;
342                 }
343
344             }
345             if (!found) {
346                 DocumentModel dm = mySession.createDocumentModel(pathStr, name,
347                         "HiddenFolder");
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);
353                 parent = dm;
354             }
355         }
356
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());
367
368         return commentDocModel;
369     }
370
371     private static void notifyEvent(CoreSession session, DocumentModel docModel, String eventType,
372             DocumentModel parent, DocumentModel child, NuxeoPrincipal principal)
373             throws ClientException {
374
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);
379         }
380         props.put(QuoteConstants.COMMENT, child);
381         props.put(QuoteConstants.COMMENT_TEXT, (String) child.getProperty(
382                 "comment", "text"));
383         props.put("category", QuoteConstants.EVENT_COMMENT_CATEGORY);
384         ctx.setProperties(props);
385         Event event = ctx.newEvent(eventType);
386
387         try {
388             EventProducer evtProducer = Framework.getService(EventProducer.class);
389             evtProducer.fireEvent(event);
390         } catch (Exception e) {
391             log.error("Error while send message", e);
392         }
393         // send also a synchronous Seam message so the CommentManagerActionBean
394         // can rebuild its list
395         // Events.instance().raiseEvent(eventType, docModel);
396     }
397
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,
407                 grantRemove });
408         acp.addACL(acl);
409         try {
410             dm.setACP(acp, true);
411         } catch (ClientException e) {
412             throw new ClientRuntimeException(e);
413         }
414     }
415
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 });
423         acp.addACL(acl);
424         try {
425             dm.setACP(acp, true);
426         } catch (ClientException e) {
427             throw new ClientRuntimeException(e);
428         }
429     }
430
431     private String[] getQuotePathList(DocumentModel comment) {
432         String[] pathList = new String[2];
433         pathList[0] = COMMENTS_DIRECTORY;
434
435         pathList[1] = dateFormat.format(getQuoteTimeStamp(comment));
436         return pathList;
437     }
438
439     private static CoreSession getUserSession(String sid) {
440         return CoreInstance.getInstance().getSession(sid);
441     }
442
443     /**
444      * @deprecated if the caller is remote, we cannot obtain the session
445      */
446     @Deprecated
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");
454         }
455         return userSession.getPrincipal().getName();
456     }
457
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);
463         }
464         Date creationDate = getQuoteTimeStamp(comment);
465         return "COMMENT-" + author + '-'
466                 + timeFormat.format(creationDate.getTime());
467     }
468
469     private static Date getQuoteTimeStamp(DocumentModel comment) {
470         Calendar creationDate;
471         try {
472             creationDate = (Calendar) comment.getProperty("dublincore",
473                     "created");
474         } catch (ClientException e) {
475             creationDate = null;
476         }
477         if (creationDate == null) {
478             creationDate = Calendar.getInstance();
479         }
480         return creationDate.getTime();
481     }
482
483     public void deleteQuote(DocumentModel docModel, DocumentModel comment)
484             throws ClientException {
485         LoginContext loginContext = null;
486         CoreSession session = null;
487         try {
488             loginContext = Framework.login();
489             session = openCoreSession(docModel.getRepositoryName());
490
491             if (session == null) {
492                 throw new ClientException(
493                         "Unable to acess repository for comment: "
494                                 + comment.getId());
495             }
496             DocumentRef ref = comment.getRef();
497             if (!session.exists(ref)) {
498                 throw new ClientException("Comment Document does not exist: "
499                         + comment.getId());
500             }
501
502             NuxeoPrincipal author = getAuthor(comment);
503             session.removeDocument(ref);
504
505             notifyEvent(session, docModel, QuoteEvents.COMMENT_REMOVED, null, comment,
506                     author);
507
508             session.save();
509
510         } catch (Throwable e) {
511             log.error("failed to delete comment", e);
512             throw new ClientException("failed to delete comment", e);
513         } finally {
514             closeCoreSession(loginContext, session);
515         }
516     }
517
518     public DocumentModel createQuote(DocumentModel docModel,
519             DocumentModel parent, DocumentModel child) throws ClientException {
520         LoginContext loginContext = null;
521         CoreSession session = null;
522         try {
523             loginContext = Framework.login();
524             session = openCoreSession(docModel.getRepositoryName());
525
526             String author = updateAuthor(docModel, child);
527             DocumentModel parentDocModel = session.getDocument(parent.getRef());
528             DocumentModel newComment = internalCreateQuote(session, parentDocModel,
529                     child, null);
530
531             UserManager userManager = Framework.getService(UserManager.class);
532             NuxeoPrincipal principal = userManager.getPrincipal(author);
533             notifyEvent(session, docModel, QuoteEvents.COMMENT_ADDED, parent,
534                     newComment, principal);
535
536             session.save();
537             return newComment;
538
539         } catch (Exception e) {
540             throw new ClientException(e);
541         } finally {
542             closeCoreSession(loginContext, session);
543         }
544     }
545
546     private static NuxeoPrincipal getAuthor(DocumentModel docModel) {
547         try {
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);
554             return null;
555         }
556     }
557
558     public List<DocumentModel> getQuotes(DocumentModel docModel,
559             DocumentModel parent) throws ClientException {
560         try {
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);
567         }
568     }
569
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());
575         try {
576             relationManager = getRelationManager();
577         } catch (Exception e) {
578             throw new ClientException(e);
579         }
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");
586         }
587         Resource predicate = new ResourceImpl(config.predicateNamespace);
588         Statement pattern = new StatementImpl(commentResource, predicate, null);
589
590         List<Statement> statementList = relationManager.getStatements(
591                 config.graphName, pattern);
592         // XXX AT: BBB for when repository name was not included in the resource
593         // uri
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,
598                 oldPattern));
599
600         List<DocumentModel> docList = new ArrayList<DocumentModel>();
601         for (Statement stmt : statementList) {
602             QNameResourceImpl subject = (QNameResourceImpl) stmt.getObject();
603             DocumentModel docModel = null;
604             try {
605                 docModel = (DocumentModel) relationManager.getResourceRepresentation(
606                         config.documentNamespace, subject, ctxMap);
607             } catch (Exception e) {
608                 log.error("failed to retrieve documents from relations");
609             }
610             if (docModel == null) {
611                 log.warn("Could not adapt comment relation subject to a document "
612                         + "model; check the service relation adapters configuration");
613                 continue;
614             }
615             docList.add(docModel);
616         }
617         return docList;
618
619     }
620     public DocumentModel createLocatedQuote(DocumentModel docModel,
621             DocumentModel comment, String path) throws ClientException {
622         LoginContext loginContext = null;
623         CoreSession session = null;
624         DocumentModel createdComment;
625         try {
626             loginContext = Framework.login();
627             session = openCoreSession(docModel.getRepositoryName());
628             createdComment = internalCreateQuote(session, docModel, comment, path);
629             session.save();
630         } catch (Exception e) {
631             throw new ClientException(e);
632         } finally {
633             closeCoreSession(loginContext, session);
634         }
635
636         return createdComment;
637     }
638
639 }