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