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