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}