001/*
002 * (C) Copyright 2014 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 *     looping
018 */
019package org.nuxeo.ecm.platform.relations.services;
020
021import java.io.Serializable;
022import java.util.Date;
023import java.util.HashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.lang.StringUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.ecm.core.api.CoreSession;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.event.CoreEventConstants;
034import org.nuxeo.ecm.core.event.EventProducer;
035import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
036import org.nuxeo.ecm.platform.relations.api.DocumentRelationManager;
037import org.nuxeo.ecm.platform.relations.api.Graph;
038import org.nuxeo.ecm.platform.relations.api.Literal;
039import org.nuxeo.ecm.platform.relations.api.Node;
040import org.nuxeo.ecm.platform.relations.api.QNameResource;
041import org.nuxeo.ecm.platform.relations.api.RelationManager;
042import org.nuxeo.ecm.platform.relations.api.Resource;
043import org.nuxeo.ecm.platform.relations.api.Statement;
044import org.nuxeo.ecm.platform.relations.api.event.RelationEvents;
045import org.nuxeo.ecm.platform.relations.api.exceptions.RelationAlreadyExistsException;
046import org.nuxeo.ecm.platform.relations.api.impl.LiteralImpl;
047import org.nuxeo.ecm.platform.relations.api.impl.RelationDate;
048import org.nuxeo.ecm.platform.relations.api.impl.ResourceImpl;
049import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl;
050import org.nuxeo.ecm.platform.relations.api.util.RelationConstants;
051import org.nuxeo.runtime.api.Framework;
052
053/**
054 * @since 5.9.2
055 */
056public class DocumentRelationService implements DocumentRelationManager {
057
058    private static final Log log = LogFactory.getLog(DocumentRelationService.class);
059
060    private RelationManager relationManager = null;
061
062    protected RelationManager getRelationManager() {
063        if (relationManager == null) {
064            relationManager = Framework.getLocalService(RelationManager.class);
065        }
066        return relationManager;
067    }
068
069    // for consistency for callers only
070    private static void putStatements(Map<String, Serializable> options, List<Statement> statements) {
071        options.put(RelationEvents.STATEMENTS_EVENT_KEY, (Serializable) statements);
072    }
073
074    private static void putStatements(Map<String, Serializable> options, Statement statement) {
075        List<Statement> statements = new LinkedList<Statement>();
076        statements.add(statement);
077        options.put(RelationEvents.STATEMENTS_EVENT_KEY, (Serializable) statements);
078    }
079
080    private QNameResource getNodeFromDocumentModel(DocumentModel model) {
081        return (QNameResource) getRelationManager().getResource(RelationConstants.DOCUMENT_NAMESPACE, model, null);
082    }
083
084    @Override
085    public void addRelation(CoreSession session, DocumentModel from, DocumentModel to, String predicate, boolean inverse)
086            {
087        addRelation(session, from, getNodeFromDocumentModel(to), predicate, inverse);
088    }
089
090    @Override
091    public void addRelation(CoreSession session, DocumentModel from, Node to, String predicate) {
092        addRelation(session, from, to, predicate, false);
093    }
094
095    @Override
096    public void addRelation(CoreSession session, DocumentModel from, Node to, String predicate, boolean inverse)
097            {
098        addRelation(session, from, to, predicate, inverse, false);
099    }
100
101    @Override
102    public void addRelation(CoreSession session, DocumentModel from, Node to, String predicate, boolean inverse,
103            boolean includeStatementsInEvents) {
104        addRelation(session, from, to, predicate, inverse, includeStatementsInEvents, null);
105    }
106
107    @Override
108    public void addRelation(CoreSession session, DocumentModel from, Node toResource, String predicate,
109            boolean inverse, boolean includeStatementsInEvents, String comment) {
110        Graph graph = getRelationManager().getGraph(RelationConstants.GRAPH_NAME, session);
111        QNameResource fromResource = getNodeFromDocumentModel(from);
112
113        Resource predicateResource = new ResourceImpl(predicate);
114        Statement stmt = null;
115        List<Statement> statements = null;
116        if (inverse) {
117            stmt = new StatementImpl(toResource, predicateResource, fromResource);
118            statements = graph.getStatements(toResource, predicateResource, fromResource);
119            if (statements != null && statements.size() > 0) {
120                throw new RelationAlreadyExistsException();
121            }
122        } else {
123            stmt = new StatementImpl(fromResource, predicateResource, toResource);
124            statements = graph.getStatements(fromResource, predicateResource, toResource);
125            if (statements != null && statements.size() > 0) {
126                throw new RelationAlreadyExistsException();
127            }
128        }
129
130        // Comment ?
131        if (!StringUtils.isEmpty(comment)) {
132            stmt.addProperty(RelationConstants.COMMENT, new LiteralImpl(comment));
133        }
134        Literal now = RelationDate.getLiteralDate(new Date());
135        if (stmt.getProperties(RelationConstants.CREATION_DATE) == null) {
136            stmt.addProperty(RelationConstants.CREATION_DATE, now);
137        }
138        if (stmt.getProperties(RelationConstants.MODIFICATION_DATE) == null) {
139            stmt.addProperty(RelationConstants.MODIFICATION_DATE, now);
140        }
141
142        if (session.getPrincipal() != null && stmt.getProperty(RelationConstants.AUTHOR) == null) {
143            stmt.addProperty(RelationConstants.AUTHOR, new LiteralImpl(session.getPrincipal().getName()));
144        }
145
146        // notifications
147
148        Map<String, Serializable> options = new HashMap<String, Serializable>();
149        String currentLifeCycleState = from.getCurrentLifeCycleState();
150        options.put(CoreEventConstants.DOC_LIFE_CYCLE, currentLifeCycleState);
151        if (includeStatementsInEvents) {
152            putStatements(options, stmt);
153        }
154        options.put(RelationEvents.GRAPH_NAME_EVENT_KEY, RelationConstants.GRAPH_NAME);
155
156        // before notification
157        notifyEvent(RelationEvents.BEFORE_RELATION_CREATION, from, options, comment, session);
158
159        // add statement
160        graph.add(stmt);
161
162        // XXX AT: try to refetch it from the graph so that resources are
163        // transformed into qname resources: useful for indexing
164        if (includeStatementsInEvents) {
165            putStatements(options, graph.getStatements(stmt));
166        }
167
168        // after notification
169        notifyEvent(RelationEvents.AFTER_RELATION_CREATION, from, options, comment, session);
170    }
171
172    protected void notifyEvent(String eventId, DocumentModel source, Map<String, Serializable> options, String comment,
173            CoreSession session) {
174        DocumentEventContext docCtx = new DocumentEventContext(session, session.getPrincipal(), source);
175        options.put("category", RelationEvents.CATEGORY);
176        options.put("comment", comment);
177
178        EventProducer evtProducer = Framework.getService(EventProducer.class);
179        evtProducer.fireEvent(docCtx.newEvent(eventId));
180    }
181
182    @Override
183    public void deleteRelation(CoreSession session, DocumentModel from, DocumentModel to, String predicate)
184            {
185        deleteRelation(session, from, to, predicate, false);
186    }
187
188    @Override
189    public void deleteRelation(CoreSession session, DocumentModel from, DocumentModel to, String predicate,
190            boolean includeStatementsInEvents) {
191        QNameResource fromResource = (QNameResource) getRelationManager().getResource(
192                RelationConstants.DOCUMENT_NAMESPACE, from, null);
193        QNameResource toResource = (QNameResource) getRelationManager().getResource(
194                RelationConstants.DOCUMENT_NAMESPACE, to, null);
195        Resource predicateResource = new ResourceImpl(predicate);
196        Graph graph = getRelationManager().getGraphByName(RelationConstants.GRAPH_NAME);
197        List<Statement> statements = graph.getStatements(fromResource, predicateResource, toResource);
198        if (statements == null || statements.size() == 0) {
199            // Silent ignore the deletion as it doesn't exist
200            return;
201        }
202        for (Statement stmt : statements) {
203            deleteRelation(session, stmt);
204        }
205    }
206
207    @Override
208    public void deleteRelation(CoreSession session, Statement stmt) {
209        deleteRelation(session, stmt, false);
210    }
211
212    @Override
213    public void deleteRelation(CoreSession session, Statement stmt, boolean includeStatementsInEvents)
214            {
215
216        // notifications
217        Map<String, Serializable> options = new HashMap<String, Serializable>();
218
219        // Find relative document
220        DocumentModel eventDocument = null;
221        if (stmt.getSubject() instanceof QNameResource) {
222            eventDocument = (DocumentModel) getRelationManager().getResourceRepresentation(
223                    RelationConstants.DOCUMENT_NAMESPACE, (QNameResource) stmt.getSubject(), null);
224        } else if (stmt.getObject() instanceof QNameResource) {
225            eventDocument = (DocumentModel) getRelationManager().getResourceRepresentation(
226                    RelationConstants.DOCUMENT_NAMESPACE, (QNameResource) stmt.getObject(), null);
227        }
228
229        // Complete event info and send first event
230        if (eventDocument != null) {
231            String currentLifeCycleState = eventDocument.getCurrentLifeCycleState();
232            options.put(CoreEventConstants.DOC_LIFE_CYCLE, currentLifeCycleState);
233            options.put(RelationEvents.GRAPH_NAME_EVENT_KEY, RelationConstants.GRAPH_NAME);
234            if (includeStatementsInEvents) {
235                putStatements(options, stmt);
236            }
237
238            // before notification
239            notifyEvent(RelationEvents.BEFORE_RELATION_REMOVAL, eventDocument, options, null, session);
240        }
241
242        // remove statement
243        getRelationManager().getGraphByName(RelationConstants.GRAPH_NAME).remove(stmt);
244
245        if (eventDocument != null) {
246            // after notification
247            notifyEvent(RelationEvents.AFTER_RELATION_REMOVAL, eventDocument, options, null, session);
248        }
249    }
250}