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.getService(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}