001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id: RelationActionsBean.java 28951 2008-01-11 13:35:15Z tdelprat $
018 */
019
020package org.nuxeo.ecm.platform.relations.web.listener.ejb;
021
022import static org.jboss.seam.ScopeType.CONVERSATION;
023
024import java.io.Serializable;
025import java.security.Principal;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.Comparator;
029import java.util.List;
030import java.util.Map;
031
032import javax.faces.event.ActionEvent;
033
034import org.apache.commons.lang.StringUtils;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.jboss.seam.ScopeType;
038import org.jboss.seam.annotations.Factory;
039import org.jboss.seam.annotations.In;
040import org.jboss.seam.annotations.Name;
041import org.jboss.seam.annotations.Scope;
042import org.jboss.seam.contexts.Context;
043import org.jboss.seam.contexts.Contexts;
044import org.jboss.seam.faces.FacesMessages;
045import org.jboss.seam.international.StatusMessage;
046import org.nuxeo.ecm.core.api.CoreSession;
047import org.nuxeo.ecm.core.api.DocumentModel;
048import org.nuxeo.ecm.platform.relations.api.DocumentRelationManager;
049import org.nuxeo.ecm.platform.relations.api.Graph;
050import org.nuxeo.ecm.platform.relations.api.Node;
051import org.nuxeo.ecm.platform.relations.api.QNameResource;
052import org.nuxeo.ecm.platform.relations.api.RelationManager;
053import org.nuxeo.ecm.platform.relations.api.Resource;
054import org.nuxeo.ecm.platform.relations.api.ResourceAdapter;
055import org.nuxeo.ecm.platform.relations.api.Statement;
056import org.nuxeo.ecm.platform.relations.api.Subject;
057import org.nuxeo.ecm.platform.relations.api.exceptions.RelationAlreadyExistsException;
058import org.nuxeo.ecm.platform.relations.api.impl.LiteralImpl;
059import org.nuxeo.ecm.platform.relations.api.impl.QNameResourceImpl;
060import org.nuxeo.ecm.platform.relations.api.impl.ResourceImpl;
061import org.nuxeo.ecm.platform.relations.api.util.RelationConstants;
062import org.nuxeo.ecm.platform.relations.jena.JenaGraph;
063import org.nuxeo.ecm.platform.relations.web.NodeInfo;
064import org.nuxeo.ecm.platform.relations.web.NodeInfoImpl;
065import org.nuxeo.ecm.platform.relations.web.StatementInfo;
066import org.nuxeo.ecm.platform.relations.web.StatementInfoComparator;
067import org.nuxeo.ecm.platform.relations.web.StatementInfoImpl;
068import org.nuxeo.ecm.platform.relations.web.listener.RelationActions;
069import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
070import org.nuxeo.ecm.platform.ui.web.invalidations.AutomaticDocumentBasedInvalidation;
071import org.nuxeo.ecm.platform.ui.web.invalidations.DocumentContextBoundActionBean;
072import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
073
074/**
075 * Seam component that manages statements involving current document as well as creation, edition and deletion of
076 * statements involving current document.
077 * <p>
078 * Current document is the subject of the relation. The predicate is resolved thanks to a list of predicates URIs. The
079 * object is resolved using a type (literal, resource, qname resource), an optional namespace (for qname resources) and
080 * a value.
081 *
082 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
083 */
084@Name("relationActions")
085@Scope(CONVERSATION)
086@AutomaticDocumentBasedInvalidation
087public class RelationActionsBean extends DocumentContextBoundActionBean implements RelationActions, Serializable {
088
089    private static final long serialVersionUID = 2336539966097558178L;
090
091    private static final Log log = LogFactory.getLog(RelationActionsBean.class);
092
093    protected static boolean includeStatementsInEvents = false;
094
095    @In(create = true, required = false)
096    protected transient CoreSession documentManager;
097
098    @In(create = true)
099    protected RelationManager relationManager;
100
101    @In(create = true)
102    protected DocumentRelationManager documentRelationManager;
103
104    @In(create = true)
105    protected NavigationContext navigationContext;
106
107    @In(create = true)
108    protected transient ResourcesAccessor resourcesAccessor;
109
110    @In(create = true, required = false)
111    protected FacesMessages facesMessages;
112
113    @In(required = false)
114    protected transient Principal currentUser;
115
116    // statements lists
117    protected List<Statement> incomingStatements;
118
119    protected List<StatementInfo> incomingStatementsInfo;
120
121    protected List<Statement> outgoingStatements;
122
123    protected List<StatementInfo> outgoingStatementsInfo;
124
125    // fields for relation creation
126
127    protected String predicateUri;
128
129    protected String objectType;
130
131    protected String objectLiteralValue;
132
133    protected String objectUri;
134
135    protected String objectDocumentUid;
136
137    protected String objectDocumentTitle;
138
139    protected String comment;
140
141    protected Boolean showCreateForm = false;
142
143    // popupDisplayed flag for preventing relation_search content view execution
144    // until search button clicked
145    protected Boolean popupDisplayed = false;
146
147    @Override
148    public DocumentModel getDocumentModel(Node node) {
149        if (node.isQNameResource()) {
150            QNameResource resource = (QNameResource) node;
151            Map<String, Object> context = Collections.<String, Object> singletonMap(
152                    ResourceAdapter.CORE_SESSION_CONTEXT_KEY, documentManager);
153            Object o = relationManager.getResourceRepresentation(resource.getNamespace(), resource, context);
154            if (o instanceof DocumentModel) {
155                return (DocumentModel) o;
156            }
157        }
158        return null;
159    }
160
161    // XXX AT: for BBB when repo name was not included in the resource uri
162    @Deprecated
163    private static QNameResource getOldDocumentResource(DocumentModel document) {
164        QNameResource documentResource = null;
165        if (document != null) {
166            documentResource = new QNameResourceImpl(RelationConstants.DOCUMENT_NAMESPACE, document.getId());
167        }
168        return documentResource;
169    }
170
171    @Override
172    public QNameResource getDocumentResource(DocumentModel document) {
173        QNameResource documentResource = null;
174        if (document != null) {
175            documentResource = (QNameResource) relationManager.getResource(RelationConstants.DOCUMENT_NAMESPACE,
176                    document, null);
177        }
178        return documentResource;
179    }
180
181    protected List<StatementInfo> getStatementsInfo(List<Statement> statements) {
182        if (statements == null) {
183            return null;
184        }
185        List<StatementInfo> infoList = new ArrayList<StatementInfo>();
186        for (Statement statement : statements) {
187            Subject subject = statement.getSubject();
188            // TODO: filter on doc visibility (?)
189            NodeInfo subjectInfo = new NodeInfoImpl(subject, getDocumentModel(subject), true);
190            Resource predicate = statement.getPredicate();
191            Node object = statement.getObject();
192            NodeInfo objectInfo = new NodeInfoImpl(object, getDocumentModel(object), true);
193            StatementInfo info = new StatementInfoImpl(statement, subjectInfo, new NodeInfoImpl(predicate), objectInfo);
194            infoList.add(info);
195        }
196        return infoList;
197    }
198
199    protected void resetEventContext() {
200        Context evtCtx = Contexts.getEventContext();
201        if (evtCtx != null) {
202            evtCtx.remove("currentDocumentIncomingRelations");
203            evtCtx.remove("currentDocumentOutgoingRelations");
204        }
205    }
206
207    @Override
208    @Factory(value = "currentDocumentIncomingRelations", scope = ScopeType.EVENT)
209    public List<StatementInfo> getIncomingStatementsInfo() {
210        if (incomingStatementsInfo != null) {
211            return incomingStatementsInfo;
212        }
213        DocumentModel currentDoc = getCurrentDocument();
214        Resource docResource = getDocumentResource(currentDoc);
215        if (docResource == null) {
216            incomingStatements = Collections.emptyList();
217            incomingStatementsInfo = Collections.emptyList();
218        } else {
219            Graph graph = relationManager.getGraphByName(RelationConstants.GRAPH_NAME);
220            incomingStatements = graph.getStatements(null, null, docResource);
221            if (graph instanceof JenaGraph) {
222                // add old statements, BBB
223                Resource oldDocResource = getOldDocumentResource(currentDoc);
224                incomingStatements.addAll(graph.getStatements(null, null, oldDocResource));
225            }
226            incomingStatementsInfo = getStatementsInfo(incomingStatements);
227            // sort by modification date, reverse
228            Comparator<StatementInfo> comp = Collections.reverseOrder(new StatementInfoComparator());
229            Collections.sort(incomingStatementsInfo, comp);
230        }
231        return incomingStatementsInfo;
232    }
233
234    @Override
235    @Factory(value = "currentDocumentOutgoingRelations", scope = ScopeType.EVENT)
236    public List<StatementInfo> getOutgoingStatementsInfo() {
237        if (outgoingStatementsInfo != null) {
238            return outgoingStatementsInfo;
239        }
240        DocumentModel currentDoc = getCurrentDocument();
241        Resource docResource = getDocumentResource(currentDoc);
242        if (docResource == null) {
243            outgoingStatements = Collections.emptyList();
244            outgoingStatementsInfo = Collections.emptyList();
245        } else {
246            Graph graph = relationManager.getGraphByName(RelationConstants.GRAPH_NAME);
247            outgoingStatements = graph.getStatements(docResource, null, null);
248            if (graph instanceof JenaGraph) {
249                // add old statements, BBB
250                Resource oldDocResource = getOldDocumentResource(currentDoc);
251                outgoingStatements.addAll(graph.getStatements(oldDocResource, null, null));
252            }
253            outgoingStatementsInfo = getStatementsInfo(outgoingStatements);
254            // sort by modification date, reverse
255            Comparator<StatementInfo> comp = Collections.reverseOrder(new StatementInfoComparator());
256            Collections.sort(outgoingStatementsInfo, comp);
257        }
258        return outgoingStatementsInfo;
259    }
260
261    @Override
262    public void resetStatements() {
263        incomingStatements = null;
264        incomingStatementsInfo = null;
265        outgoingStatements = null;
266        outgoingStatementsInfo = null;
267    }
268
269    // getters & setters for creation items
270
271    @Override
272    public String getComment() {
273        return comment;
274    }
275
276    @Override
277    public void setComment(String comment) {
278        this.comment = comment;
279    }
280
281    @Override
282    public String getObjectDocumentTitle() {
283        return objectDocumentTitle;
284    }
285
286    @Override
287    public void setObjectDocumentTitle(String objectDocumentTitle) {
288        this.objectDocumentTitle = objectDocumentTitle;
289    }
290
291    @Override
292    public String getObjectDocumentUid() {
293        return objectDocumentUid;
294    }
295
296    @Override
297    public void setObjectDocumentUid(String objectDocumentUid) {
298        this.objectDocumentUid = objectDocumentUid;
299    }
300
301    @Override
302    public String getObjectLiteralValue() {
303        return objectLiteralValue;
304    }
305
306    @Override
307    public void setObjectLiteralValue(String objectLiteralValue) {
308        this.objectLiteralValue = objectLiteralValue;
309    }
310
311    @Override
312    public String getObjectType() {
313        return objectType;
314    }
315
316    @Override
317    public void setObjectType(String objectType) {
318        this.objectType = objectType;
319    }
320
321    @Override
322    public String getObjectUri() {
323        return objectUri;
324    }
325
326    @Override
327    public void setObjectUri(String objectUri) {
328        this.objectUri = objectUri;
329    }
330
331    @Override
332    public String getPredicateUri() {
333        return predicateUri;
334    }
335
336    @Override
337    public void setPredicateUri(String predicateUri) {
338        this.predicateUri = predicateUri;
339    }
340
341    @Override
342    public String addStatement() {
343        resetEventContext();
344
345        Node object = null;
346        if (objectType.equals("literal")) {
347            objectLiteralValue = objectLiteralValue.trim();
348            object = new LiteralImpl(objectLiteralValue);
349        } else if (objectType.equals("uri")) {
350            objectUri = objectUri.trim();
351            object = new ResourceImpl(objectUri);
352        } else if (objectType.equals("document")) {
353            objectDocumentUid = objectDocumentUid.trim();
354            String repositoryName = navigationContext.getCurrentServerLocation().getName();
355            String localName = repositoryName + "/" + objectDocumentUid;
356            object = new QNameResourceImpl(RelationConstants.DOCUMENT_NAMESPACE, localName);
357        }
358        try {
359            documentRelationManager.addRelation(documentManager, getCurrentDocument(), object, predicateUri, false,
360                    includeStatementsInEvents, StringUtils.trim(comment));
361            facesMessages.add(StatusMessage.Severity.INFO,
362                    resourcesAccessor.getMessages().get("label.relation.created"));
363            resetCreateFormValues();
364        } catch (RelationAlreadyExistsException e) {
365            facesMessages.add(StatusMessage.Severity.WARN,
366                    resourcesAccessor.getMessages().get("label.relation.already.exists"));
367        }
368        resetStatements();
369        return null;
370    }
371
372    @Override
373    public void toggleCreateForm(ActionEvent event) {
374        showCreateForm = !showCreateForm;
375    }
376
377    private void resetCreateFormValues() {
378        predicateUri = "";
379        objectType = "";
380        objectLiteralValue = "";
381        objectUri = "";
382        objectDocumentUid = "";
383        objectDocumentTitle = "";
384        comment = "";
385        showCreateForm = false;
386        popupDisplayed = false;
387    }
388
389    @Override
390    public String deleteStatement(StatementInfo stmtInfo) {
391        resetEventContext();
392        documentRelationManager.deleteRelation(documentManager, stmtInfo.getStatement());
393        facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("label.relation.deleted"));
394        resetStatements();
395        return null;
396    }
397
398    @Override
399    public Boolean getShowCreateForm() {
400        return showCreateForm;
401    }
402
403    @Override
404    protected void resetBeanCache(DocumentModel newCurrentDocumentModel) {
405        resetStatements();
406    }
407
408    public Boolean getPopupDisplayed() {
409        return popupDisplayed;
410    }
411
412    public void setPopupDisplayed(Boolean popupDisplayed) {
413        this.popupDisplayed = popupDisplayed;
414    }
415
416}