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