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