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