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<Statement>(); 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<Statement>(); 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 try (CoreSession session = CoreInstance.openCoreSessionSystem(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<DocumentRef, Set<Resource>>(); 249 List<Statement> statements = new ArrayList<Statement>(); 250 Set<Resource> allResources = new HashSet<Resource>(); 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<Statement>(); 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 291 @Override 292 public void getResourcesAsXML(OutputStream out, IOResources resources) { 293 if (!(resources instanceof IORelationResources)) { 294 return; 295 } 296 IORelationResources relResources = (IORelationResources) resources; 297 Map<String, String> namespaces = relResources.getNamespaces(); 298 List<Statement> statements = relResources.getStatements(); 299 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, statements); 300 graphHelper.write(out); 301 } 302 303 private void addResourceEntry(RelationManager relManager, Map<DocumentRef, Set<Resource>> map, Node node) { 304 if (!node.isQNameResource()) { 305 return; 306 } 307 QNameResource resource = (QNameResource) node; 308 DocumentRef docRef = getDocumentRef(relManager, resource); 309 if (docRef == null) { 310 return; 311 } 312 if (map.containsKey(docRef)) { 313 map.get(docRef).add(resource); 314 } else { 315 Set<Resource> set = new HashSet<Resource>(); 316 set.add(resource); 317 map.put(docRef, set); 318 } 319 } 320 321 @Override 322 public IOResources loadResourcesFromXML(InputStream in) { 323 RelationManager relManager = getRelationManager(); 324 String graphName = getStringProperty(IORelationAdapterProperties.IMPORT_GRAPH); 325 if (graphName == null) { 326 graphName = getStringProperty(IORelationAdapterProperties.GRAPH); 327 } 328 // XXX find target graph to retrieve namespaces 329 Map<String, String> namespaces = null; 330 if (graphName != null) { 331 Graph graph = relManager.getGraphByName(graphName); 332 if (graph != null) { 333 namespaces = graph.getNamespaces(); 334 } 335 } 336 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, null); 337 graphHelper.read(in); 338 // find documents related to given statements 339 List<Statement> statements = filterMatchingStatements(graphHelper.getStatements()); 340 Map<DocumentRef, Set<Resource>> docResources = new HashMap<DocumentRef, Set<Resource>>(); 341 for (Statement statement : statements) { 342 Subject subject = statement.getSubject(); 343 addResourceEntry(relManager, docResources, subject); 344 Node object = statement.getObject(); 345 addResourceEntry(relManager, docResources, object); 346 } 347 return new IORelationResources(namespaces, docResources, statements); 348 } 349 350 @Override 351 public void storeResources(IOResources resources) { 352 if (!(resources instanceof IORelationResources)) { 353 return; 354 } 355 IORelationResources relResources = (IORelationResources) resources; 356 String graphName = getStringProperty(IORelationAdapterProperties.IMPORT_GRAPH); 357 if (graphName == null) { 358 graphName = getStringProperty(IORelationAdapterProperties.GRAPH); 359 } 360 if (graphName == null) { 361 log.error("Cannot find graph name"); 362 return; 363 } 364 RelationManager relManager = getRelationManager(); 365 Graph graph = relManager.getGraphByName(graphName); 366 if (graph == null) { 367 log.error("Cannot find graph with name " + graphName); 368 return; 369 } 370 graph.add(relResources.getStatements()); 371 } 372 373 protected static Statement updateDate(Statement statement, Literal newDate, List<Resource> properties) { 374 for (Resource property : properties) { 375 // do not update if not present 376 if (statement.getProperty(property) != null) { 377 statement.setProperty(property, newDate); 378 } 379 } 380 return statement; 381 } 382 383 @Override 384 public IOResources translateResources(String repo, IOResources resources, DocumentTranslationMap map) { 385 if (map == null) { 386 return null; 387 } 388 if (!(resources instanceof IORelationResources)) { 389 return resources; 390 } 391 try (CoreSession session = CoreInstance.openCoreSessionSystem(repo)) { 392 IORelationResources relResources = (IORelationResources) resources; 393 Map<String, String> namespaces = relResources.getNamespaces(); 394 IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, relResources.getStatements()); 395 Graph graph = graphHelper.getGraph(); 396 RelationManager relManager = getRelationManager(); 397 // variables for date update 398 Literal newDate = RelationDate.getLiteralDate(new Date()); 399 String[] dateUris = getStringArrayProperty(IORelationAdapterProperties.UPDATE_DATE_METADATA); 400 List<Resource> dateProperties = new ArrayList<Resource>(); 401 if (dateUris != null) { 402 for (String dateUri : dateUris) { 403 dateProperties.add(new ResourceImpl(dateUri)); 404 } 405 } 406 for (Map.Entry<DocumentRef, Set<Resource>> entry : relResources.getResourcesMap().entrySet()) { 407 DocumentRef oldRef = entry.getKey(); 408 DocumentRef newRef = map.getDocRefMap().get(oldRef); 409 Set<Resource> docResources = relResources.getDocumentResources(oldRef); 410 for (Resource resource : docResources) { 411 if (!resource.isQNameResource() || oldRef.equals(newRef)) { 412 // cannot translate or no change => keep same 413 continue; 414 } 415 Statement pattern = new StatementImpl(resource, null, null); 416 List<Statement> outgoing = graph.getStatements(pattern); 417 pattern = new StatementImpl(null, null, resource); 418 List<Statement> incoming = graph.getStatements(pattern); 419 420 // remove old statements 421 graph.remove(outgoing); 422 graph.remove(incoming); 423 424 if (newRef == null) { 425 // do not replace 426 continue; 427 } 428 429 DocumentModel newDoc; 430 try { 431 newDoc = session.getDocument(newRef); 432 } catch (DocumentNotFoundException e) { 433 // do not replace 434 continue; 435 } 436 QNameResource qnameRes = (QNameResource) resource; 437 Map<String, Object> context = Collections.<String, Object> singletonMap( 438 ResourceAdapter.CORE_SESSION_CONTEXT_KEY, session); 439 Resource newResource = relManager.getResource(qnameRes.getNamespace(), newDoc, context); 440 Statement newStatement; 441 List<Statement> newOutgoing = new ArrayList<Statement>(); 442 for (Statement stmt : outgoing) { 443 newStatement = (Statement) stmt.clone(); 444 newStatement.setSubject(newResource); 445 if (dateProperties != null) { 446 newStatement = updateDate(newStatement, newDate, dateProperties); 447 } 448 newOutgoing.add(newStatement); 449 } 450 graph.add(newOutgoing); 451 List<Statement> newIncoming = new ArrayList<Statement>(); 452 for (Statement stmt : incoming) { 453 newStatement = (Statement) stmt.clone(); 454 newStatement.setObject(newResource); 455 if (dateProperties != null) { 456 newStatement = updateDate(newStatement, newDate, dateProperties); 457 } 458 newIncoming.add(newStatement); 459 } 460 graph.add(newIncoming); 461 } 462 } 463 return new IORelationResources(namespaces, relResources.getResourcesMap(), graph.getStatements()); 464 } 465 } 466}