001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (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.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 * <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 016 * 017 * $Id: IORelationAdapter.java 26168 2007-10-18 11:21:21Z dmihalache $ 018 */ 019 020package org.nuxeo.ecm.platform.relations.io; 021 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.ecm.core.api.CoreInstance; 039import org.nuxeo.ecm.core.api.CoreSession; 040import org.nuxeo.ecm.core.api.DocumentModel; 041import org.nuxeo.ecm.core.api.DocumentNotFoundException; 042import org.nuxeo.ecm.core.api.DocumentRef; 043import org.nuxeo.ecm.core.api.IdRef; 044import org.nuxeo.ecm.core.io.DocumentTranslationMap; 045import org.nuxeo.ecm.platform.io.api.AbstractIOResourceAdapter; 046import org.nuxeo.ecm.platform.io.api.IOResources; 047import org.nuxeo.ecm.platform.relations.api.Graph; 048import org.nuxeo.ecm.platform.relations.api.Literal; 049import org.nuxeo.ecm.platform.relations.api.Node; 050import org.nuxeo.ecm.platform.relations.api.QNameResource; 051import org.nuxeo.ecm.platform.relations.api.RelationManager; 052import org.nuxeo.ecm.platform.relations.api.Resource; 053import org.nuxeo.ecm.platform.relations.api.ResourceAdapter; 054import org.nuxeo.ecm.platform.relations.api.Statement; 055import org.nuxeo.ecm.platform.relations.api.Subject; 056import org.nuxeo.ecm.platform.relations.api.impl.RelationDate; 057import org.nuxeo.ecm.platform.relations.api.impl.ResourceImpl; 058import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl; 059import org.nuxeo.runtime.api.Framework; 060 061/** 062 * Adapter for import/export of relations 063 * 064 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 065 */ 066public class IORelationAdapter extends AbstractIOResourceAdapter { 067 068 private static final Log log = LogFactory.getLog(IORelationAdapter.class); 069 070 private static final long serialVersionUID = -3661302796286246086L; 071 072 @Override 073 public void setProperties(Map<String, Serializable> properties) { 074 if (properties != null) { 075 for (Map.Entry<String, Serializable> prop : properties.entrySet()) { 076 String propName = prop.getKey(); 077 Serializable propValue = prop.getValue(); 078 if (IORelationAdapterProperties.GRAPH.equals(propName)) { 079 setStringProperty(propName, propValue); 080 } 081 if (IORelationAdapterProperties.IMPORT_GRAPH.equals(propName)) { 082 setStringProperty(propName, propValue); 083 } 084 if (IORelationAdapterProperties.IGNORE_EXTERNAL.equals(propName)) { 085 setBooleanProperty(propName, propValue); 086 } 087 if (IORelationAdapterProperties.IGNORE_LITERALS.equals(propName)) { 088 setBooleanProperty(propName, propValue); 089 } 090 if (IORelationAdapterProperties.IGNORE_SIMPLE_RESOURCES.equals(propName)) { 091 setBooleanProperty(propName, propValue); 092 } 093 if (IORelationAdapterProperties.FILTER_PREDICATES.equals(propName)) { 094 setStringArrayProperty(propName, propValue); 095 } 096 if (IORelationAdapterProperties.IGNORE_PREDICATES.equals(propName)) { 097 setStringArrayProperty(propName, propValue); 098 } 099 if (IORelationAdapterProperties.FILTER_METADATA.equals(propName)) { 100 setStringArrayProperty(propName, propValue); 101 } 102 if (IORelationAdapterProperties.IGNORE_METADATA.equals(propName)) { 103 setStringArrayProperty(propName, propValue); 104 } 105 if (IORelationAdapterProperties.IGNORE_ALL_METADATA.equals(propName)) { 106 setBooleanProperty(propName, propValue); 107 } 108 if (IORelationAdapterProperties.UPDATE_DATE_METADATA.equals(propName)) { 109 setStringArrayProperty(propName, propValue); 110 } 111 } 112 } 113 if (this.properties == null || getStringProperty(IORelationAdapterProperties.GRAPH) == null) { 114 log.warn("No graph name given for relations adapter, " + "no IO will be performed with this adapter"); 115 } 116 } 117 118 protected RelationManager getRelationManager() { 119 return Framework.getService(RelationManager.class); 120 } 121 122 protected List<Statement> getMatchingStatements(Graph graph, Resource resource) { 123 // TODO filter using properties 124 List<Statement> matching = new ArrayList<Statement>(); 125 Statement incomingPattern = new StatementImpl(null, null, resource); 126 matching.addAll(graph.getStatements(incomingPattern)); 127 Statement outgoingPattern = new StatementImpl(resource, null, null); 128 matching.addAll(graph.getStatements(outgoingPattern)); 129 return filterMatchingStatements(matching); 130 } 131 132 protected Statement getFilteredStatement(Statement statement) { 133 Subject subject = statement.getSubject(); 134 Resource predicate = statement.getPredicate(); 135 Node object = statement.getObject(); 136 if (getBooleanProperty(IORelationAdapterProperties.IGNORE_LITERALS) && object.isLiteral()) { 137 return null; 138 } 139 if (getBooleanProperty(IORelationAdapterProperties.IGNORE_SIMPLE_RESOURCES)) { 140 if (!subject.isQNameResource() || !object.isQNameResource()) { 141 return null; 142 } 143 } 144 String[] filteredPredicates = getStringArrayProperty(IORelationAdapterProperties.FILTER_PREDICATES); 145 if (filteredPredicates != null) { 146 if (!Arrays.asList(filteredPredicates).contains(predicate.getUri())) { 147 return null; 148 } 149 } 150 String[] ignoredPredicates = getStringArrayProperty(IORelationAdapterProperties.IGNORE_PREDICATES); 151 if (ignoredPredicates != null) { 152 if (Arrays.asList(ignoredPredicates).contains(predicate.getUri())) { 153 return null; 154 } 155 } 156 if (getBooleanProperty(IORelationAdapterProperties.IGNORE_ALL_METADATA)) { 157 Statement newStatement = (Statement) statement.clone(); 158 newStatement.deleteProperties(); 159 return newStatement; 160 } 161 String[] filterMetadata = getStringArrayProperty(IORelationAdapterProperties.FILTER_METADATA); 162 if (filterMetadata != null) { 163 Statement newStatement = (Statement) statement.clone(); 164 Map<Resource, Node[]> props = newStatement.getProperties(); 165 List<String> filter = Arrays.asList(filterMetadata); 166 for (Map.Entry<Resource, Node[]> prop : props.entrySet()) { 167 Resource propKey = prop.getKey(); 168 if (!filter.contains(propKey.getUri())) { 169 newStatement.deleteProperty(propKey); 170 } 171 } 172 return newStatement; 173 } 174 String[] ignoreMetadata = getStringArrayProperty(IORelationAdapterProperties.IGNORE_METADATA); 175 if (ignoreMetadata != null) { 176 Statement newStatement = (Statement) statement.clone(); 177 Map<Resource, Node[]> props = newStatement.getProperties(); 178 List<String> filter = Arrays.asList(ignoreMetadata); 179 for (Map.Entry<Resource, Node[]> prop : props.entrySet()) { 180 Resource propKey = prop.getKey(); 181 if (filter.contains(propKey.getUri())) { 182 newStatement.deleteProperty(propKey); 183 } 184 } 185 return newStatement; 186 } 187 return statement; 188 } 189 190 protected List<Statement> filterMatchingStatements(List<Statement> statements) { 191 List<Statement> newStatements = null; 192 if (statements != null) { 193 newStatements = new ArrayList<Statement>(); 194 for (Statement stmt : statements) { 195 Statement newStmt = getFilteredStatement(stmt); 196 if (newStmt != null) { 197 newStatements.add(newStmt); 198 } 199 } 200 } 201 return newStatements; 202 } 203 204 protected DocumentRef getDocumentRef(RelationManager relManager, QNameResource resource) { 205 String ns = resource.getNamespace(); 206 if ("http://www.nuxeo.org/document/uid/".equals(ns)) { 207 // BS: Avoid using default resource resolver since it is not working 208 // when 209 // the resource document is not currently existing in the target 210 // repository. 211 // TODO This is a hack and should be fixed in the lower layers or by 212 // changing 213 // import logic. 214 String id = resource.getLocalName(); 215 int p = id.indexOf('/'); 216 if (p > -1) { 217 id = id.substring(p + 1); 218 } 219 return new IdRef(id); 220 } 221 return null; 222 } 223 224 /** 225 * Extract relations involving given documents. 226 * <p> 227 * The adapter properties will filter which relations must be taken into account. 228 */ 229 @Override 230 public IOResources extractResources(String repo, Collection<DocumentRef> sources) { 231 if (sources == null || sources.isEmpty()) { 232 return null; 233 } 234 String graphName = getStringProperty(IORelationAdapterProperties.GRAPH); 235 if (graphName == null) { 236 log.error("Cannot extract resources, no graph supplied"); 237 return null; 238 } 239 try (CoreSession session = CoreInstance.openCoreSessionSystem(repo)) { 240 RelationManager relManager = getRelationManager(); 241 Graph graph = relManager.getGraphByName(graphName); 242 if (graph == null) { 243 log.error("Cannot resolve graph " + graphName); 244 return null; 245 } 246 Map<DocumentRef, Set<Resource>> docResources = new HashMap<DocumentRef, Set<Resource>>(); 247 List<Statement> statements = new ArrayList<Statement>(); 248 Set<Resource> allResources = new HashSet<Resource>(); 249 for (DocumentRef docRef : sources) { 250 DocumentModel doc = session.getDocument(docRef); 251 Map<String, Object> context = Collections.<String, Object> singletonMap( 252 ResourceAdapter.CORE_SESSION_CONTEXT_KEY, session); 253 Set<Resource> resources = relManager.getAllResources(doc, context); 254 docResources.put(docRef, resources); 255 allResources.addAll(resources); 256 for (Resource resource : resources) { 257 statements.addAll(getMatchingStatements(graph, resource)); 258 } 259 } 260 Map<String, String> namespaces = graph.getNamespaces(); 261 // filter duplicate statements + statements involving external 262 // resources 263 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, statements); 264 Graph memoryGraph = graphHelper.getGraph(); 265 List<Statement> toRemove = new ArrayList<Statement>(); 266 if (getBooleanProperty(IORelationAdapterProperties.IGNORE_EXTERNAL)) { 267 for (Statement stmt : memoryGraph.getStatements()) { 268 Subject subject = stmt.getSubject(); 269 if (subject.isQNameResource()) { 270 if (!allResources.contains(subject)) { 271 toRemove.add(stmt); 272 continue; 273 } 274 } 275 Node object = stmt.getObject(); 276 if (object.isQNameResource()) { 277 if (!allResources.contains(subject)) { 278 toRemove.add(stmt); 279 continue; 280 } 281 } 282 } 283 } 284 memoryGraph.remove(toRemove); 285 return new IORelationResources(namespaces, docResources, memoryGraph.getStatements()); 286 } 287 } 288 289 @Override 290 public void getResourcesAsXML(OutputStream out, IOResources resources) { 291 if (!(resources instanceof IORelationResources)) { 292 return; 293 } 294 IORelationResources relResources = (IORelationResources) resources; 295 Map<String, String> namespaces = relResources.getNamespaces(); 296 List<Statement> statements = relResources.getStatements(); 297 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, statements); 298 graphHelper.write(out); 299 } 300 301 private void addResourceEntry(RelationManager relManager, Map<DocumentRef, Set<Resource>> map, Node node) { 302 if (!node.isQNameResource()) { 303 return; 304 } 305 QNameResource resource = (QNameResource) node; 306 DocumentRef docRef = getDocumentRef(relManager, resource); 307 if (docRef == null) { 308 return; 309 } 310 if (map.containsKey(docRef)) { 311 map.get(docRef).add(resource); 312 } else { 313 Set<Resource> set = new HashSet<Resource>(); 314 set.add(resource); 315 map.put(docRef, set); 316 } 317 } 318 319 @Override 320 public IOResources loadResourcesFromXML(InputStream in) { 321 RelationManager relManager = getRelationManager(); 322 String graphName = getStringProperty(IORelationAdapterProperties.IMPORT_GRAPH); 323 if (graphName == null) { 324 graphName = getStringProperty(IORelationAdapterProperties.GRAPH); 325 } 326 // XXX find target graph to retrieve namespaces 327 Map<String, String> namespaces = null; 328 if (graphName != null) { 329 Graph graph = relManager.getGraphByName(graphName); 330 if (graph != null) { 331 namespaces = graph.getNamespaces(); 332 } 333 } 334 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, null); 335 graphHelper.read(in); 336 // find documents related to given statements 337 List<Statement> statements = filterMatchingStatements(graphHelper.getStatements()); 338 Map<DocumentRef, Set<Resource>> docResources = new HashMap<DocumentRef, Set<Resource>>(); 339 for (Statement statement : statements) { 340 Subject subject = statement.getSubject(); 341 addResourceEntry(relManager, docResources, subject); 342 Node object = statement.getObject(); 343 addResourceEntry(relManager, docResources, object); 344 } 345 return new IORelationResources(namespaces, docResources, statements); 346 } 347 348 @Override 349 public void storeResources(IOResources resources) { 350 if (!(resources instanceof IORelationResources)) { 351 return; 352 } 353 IORelationResources relResources = (IORelationResources) resources; 354 String graphName = getStringProperty(IORelationAdapterProperties.IMPORT_GRAPH); 355 if (graphName == null) { 356 graphName = getStringProperty(IORelationAdapterProperties.GRAPH); 357 } 358 if (graphName == null) { 359 log.error("Cannot find graph name"); 360 return; 361 } 362 RelationManager relManager = getRelationManager(); 363 Graph graph = relManager.getGraphByName(graphName); 364 if (graph == null) { 365 log.error("Cannot find graph with name " + graphName); 366 return; 367 } 368 graph.add(relResources.getStatements()); 369 } 370 371 protected static Statement updateDate(Statement statement, Literal newDate, List<Resource> properties) { 372 for (Resource property : properties) { 373 // do not update if not present 374 if (statement.getProperty(property) != null) { 375 statement.setProperty(property, newDate); 376 } 377 } 378 return statement; 379 } 380 381 @Override 382 public IOResources translateResources(String repo, IOResources resources, DocumentTranslationMap map) { 383 if (map == null) { 384 return null; 385 } 386 if (!(resources instanceof IORelationResources)) { 387 return resources; 388 } 389 try (CoreSession session = CoreInstance.openCoreSessionSystem(repo)) { 390 IORelationResources relResources = (IORelationResources) resources; 391 Map<String, String> namespaces = relResources.getNamespaces(); 392 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, relResources.getStatements()); 393 Graph graph = graphHelper.getGraph(); 394 RelationManager relManager = getRelationManager(); 395 // variables for date update 396 Literal newDate = RelationDate.getLiteralDate(new Date()); 397 String[] dateUris = getStringArrayProperty(IORelationAdapterProperties.UPDATE_DATE_METADATA); 398 List<Resource> dateProperties = new ArrayList<Resource>(); 399 if (dateUris != null) { 400 for (String dateUri : dateUris) { 401 dateProperties.add(new ResourceImpl(dateUri)); 402 } 403 } 404 for (Map.Entry<DocumentRef, Set<Resource>> entry : relResources.getResourcesMap().entrySet()) { 405 DocumentRef oldRef = entry.getKey(); 406 DocumentRef newRef = map.getDocRefMap().get(oldRef); 407 Set<Resource> docResources = relResources.getDocumentResources(oldRef); 408 for (Resource resource : docResources) { 409 if (!resource.isQNameResource() || oldRef.equals(newRef)) { 410 // cannot translate or no change => keep same 411 continue; 412 } 413 Statement pattern = new StatementImpl(resource, null, null); 414 List<Statement> outgoing = graph.getStatements(pattern); 415 pattern = new StatementImpl(null, null, resource); 416 List<Statement> incoming = graph.getStatements(pattern); 417 418 // remove old statements 419 graph.remove(outgoing); 420 graph.remove(incoming); 421 422 if (newRef == null) { 423 // do not replace 424 continue; 425 } 426 427 DocumentModel newDoc; 428 try { 429 newDoc = session.getDocument(newRef); 430 } catch (DocumentNotFoundException e) { 431 // do not replace 432 continue; 433 } 434 QNameResource qnameRes = (QNameResource) resource; 435 Map<String, Object> context = Collections.<String, Object> singletonMap( 436 ResourceAdapter.CORE_SESSION_CONTEXT_KEY, session); 437 Resource newResource = relManager.getResource(qnameRes.getNamespace(), newDoc, context); 438 Statement newStatement; 439 List<Statement> newOutgoing = new ArrayList<Statement>(); 440 for (Statement stmt : outgoing) { 441 newStatement = (Statement) stmt.clone(); 442 newStatement.setSubject(newResource); 443 if (dateProperties != null) { 444 newStatement = updateDate(newStatement, newDate, dateProperties); 445 } 446 newOutgoing.add(newStatement); 447 } 448 graph.add(newOutgoing); 449 List<Statement> newIncoming = new ArrayList<Statement>(); 450 for (Statement stmt : incoming) { 451 newStatement = (Statement) stmt.clone(); 452 newStatement.setObject(newResource); 453 if (dateProperties != null) { 454 newStatement = updateDate(newStatement, newDate, dateProperties); 455 } 456 newIncoming.add(newStatement); 457 } 458 graph.add(newIncoming); 459 } 460 } 461 return new IORelationResources(namespaces, relResources.getResourcesMap(), graph.getStatements()); 462 } 463 } 464}