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}