/*
* (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
*
* $Id: IORelationAdapter.java 26168 2007-10-18 11:21:21Z dmihalache $
*/
package org.nuxeo.ecm.platform.relations.io;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.CoreInstance;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentNotFoundException;
import org.nuxeo.ecm.core.api.DocumentRef;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.io.DocumentTranslationMap;
import org.nuxeo.ecm.platform.io.api.AbstractIOResourceAdapter;
import org.nuxeo.ecm.platform.io.api.IOResources;
import org.nuxeo.ecm.platform.relations.api.Graph;
import org.nuxeo.ecm.platform.relations.api.Literal;
import org.nuxeo.ecm.platform.relations.api.Node;
import org.nuxeo.ecm.platform.relations.api.QNameResource;
import org.nuxeo.ecm.platform.relations.api.RelationManager;
import org.nuxeo.ecm.platform.relations.api.Resource;
import org.nuxeo.ecm.platform.relations.api.ResourceAdapter;
import org.nuxeo.ecm.platform.relations.api.Statement;
import org.nuxeo.ecm.platform.relations.api.Subject;
import org.nuxeo.ecm.platform.relations.api.impl.RelationDate;
import org.nuxeo.ecm.platform.relations.api.impl.ResourceImpl;
import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl;
import org.nuxeo.runtime.api.Framework;
/**
* Adapter for import/export of relations
*
* @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
*/
public class IORelationAdapter extends AbstractIOResourceAdapter {
private static final Log log = LogFactory.getLog(IORelationAdapter.class);
private static final long serialVersionUID = -3661302796286246086L;
@Override
public void setProperties(Map<String, Serializable> properties) {
if (properties != null) {
for (Map.Entry<String, Serializable> prop : properties.entrySet()) {
String propName = prop.getKey();
Serializable propValue = prop.getValue();
if (IORelationAdapterProperties.GRAPH.equals(propName)) {
setStringProperty(propName, propValue);
}
if (IORelationAdapterProperties.IMPORT_GRAPH.equals(propName)) {
setStringProperty(propName, propValue);
}
if (IORelationAdapterProperties.IGNORE_EXTERNAL.equals(propName)) {
setBooleanProperty(propName, propValue);
}
if (IORelationAdapterProperties.IGNORE_LITERALS.equals(propName)) {
setBooleanProperty(propName, propValue);
}
if (IORelationAdapterProperties.IGNORE_SIMPLE_RESOURCES.equals(propName)) {
setBooleanProperty(propName, propValue);
}
if (IORelationAdapterProperties.FILTER_PREDICATES.equals(propName)) {
setStringArrayProperty(propName, propValue);
}
if (IORelationAdapterProperties.IGNORE_PREDICATES.equals(propName)) {
setStringArrayProperty(propName, propValue);
}
if (IORelationAdapterProperties.FILTER_METADATA.equals(propName)) {
setStringArrayProperty(propName, propValue);
}
if (IORelationAdapterProperties.IGNORE_METADATA.equals(propName)) {
setStringArrayProperty(propName, propValue);
}
if (IORelationAdapterProperties.IGNORE_ALL_METADATA.equals(propName)) {
setBooleanProperty(propName, propValue);
}
if (IORelationAdapterProperties.UPDATE_DATE_METADATA.equals(propName)) {
setStringArrayProperty(propName, propValue);
}
}
}
if (this.properties == null || getStringProperty(IORelationAdapterProperties.GRAPH) == null) {
log.warn("No graph name given for relations adapter, " + "no IO will be performed with this adapter");
}
}
protected RelationManager getRelationManager() {
return Framework.getService(RelationManager.class);
}
protected List<Statement> getMatchingStatements(Graph graph, Resource resource) {
// TODO filter using properties
List<Statement> matching = new ArrayList<Statement>();
Statement incomingPattern = new StatementImpl(null, null, resource);
matching.addAll(graph.getStatements(incomingPattern));
Statement outgoingPattern = new StatementImpl(resource, null, null);
matching.addAll(graph.getStatements(outgoingPattern));
return filterMatchingStatements(matching);
}
protected Statement getFilteredStatement(Statement statement) {
Subject subject = statement.getSubject();
Resource predicate = statement.getPredicate();
Node object = statement.getObject();
if (getBooleanProperty(IORelationAdapterProperties.IGNORE_LITERALS) && object.isLiteral()) {
return null;
}
if (getBooleanProperty(IORelationAdapterProperties.IGNORE_SIMPLE_RESOURCES)) {
if (!subject.isQNameResource() || !object.isQNameResource()) {
return null;
}
}
String[] filteredPredicates = getStringArrayProperty(IORelationAdapterProperties.FILTER_PREDICATES);
if (filteredPredicates != null) {
if (!Arrays.asList(filteredPredicates).contains(predicate.getUri())) {
return null;
}
}
String[] ignoredPredicates = getStringArrayProperty(IORelationAdapterProperties.IGNORE_PREDICATES);
if (ignoredPredicates != null) {
if (Arrays.asList(ignoredPredicates).contains(predicate.getUri())) {
return null;
}
}
if (getBooleanProperty(IORelationAdapterProperties.IGNORE_ALL_METADATA)) {
Statement newStatement = (Statement) statement.clone();
newStatement.deleteProperties();
return newStatement;
}
String[] filterMetadata = getStringArrayProperty(IORelationAdapterProperties.FILTER_METADATA);
if (filterMetadata != null) {
Statement newStatement = (Statement) statement.clone();
Map<Resource, Node[]> props = newStatement.getProperties();
List<String> filter = Arrays.asList(filterMetadata);
for (Map.Entry<Resource, Node[]> prop : props.entrySet()) {
Resource propKey = prop.getKey();
if (!filter.contains(propKey.getUri())) {
newStatement.deleteProperty(propKey);
}
}
return newStatement;
}
String[] ignoreMetadata = getStringArrayProperty(IORelationAdapterProperties.IGNORE_METADATA);
if (ignoreMetadata != null) {
Statement newStatement = (Statement) statement.clone();
Map<Resource, Node[]> props = newStatement.getProperties();
List<String> filter = Arrays.asList(ignoreMetadata);
for (Map.Entry<Resource, Node[]> prop : props.entrySet()) {
Resource propKey = prop.getKey();
if (filter.contains(propKey.getUri())) {
newStatement.deleteProperty(propKey);
}
}
return newStatement;
}
return statement;
}
protected List<Statement> filterMatchingStatements(List<Statement> statements) {
List<Statement> newStatements = null;
if (statements != null) {
newStatements = new ArrayList<Statement>();
for (Statement stmt : statements) {
Statement newStmt = getFilteredStatement(stmt);
if (newStmt != null) {
newStatements.add(newStmt);
}
}
}
return newStatements;
}
protected DocumentRef getDocumentRef(RelationManager relManager, QNameResource resource) {
String ns = resource.getNamespace();
if ("http://www.nuxeo.org/document/uid/".equals(ns)) {
// BS: Avoid using default resource resolver since it is not working
// when
// the resource document is not currently existing in the target
// repository.
// TODO This is a hack and should be fixed in the lower layers or by
// changing
// import logic.
String id = resource.getLocalName();
int p = id.indexOf('/');
if (p > -1) {
id = id.substring(p + 1);
}
return new IdRef(id);
}
return null;
}
/**
* Extract relations involving given documents.
* <p>
* The adapter properties will filter which relations must be taken into account.
*/
@Override
public IOResources extractResources(String repo, Collection<DocumentRef> sources) {
if (sources == null || sources.isEmpty()) {
return null;
}
String graphName = getStringProperty(IORelationAdapterProperties.GRAPH);
if (graphName == null) {
log.error("Cannot extract resources, no graph supplied");
return null;
}
try (CoreSession session = CoreInstance.openCoreSessionSystem(repo)) {
RelationManager relManager = getRelationManager();
Graph graph = relManager.getGraphByName(graphName);
if (graph == null) {
log.error("Cannot resolve graph " + graphName);
return null;
}
Map<DocumentRef, Set<Resource>> docResources = new HashMap<DocumentRef, Set<Resource>>();
List<Statement> statements = new ArrayList<Statement>();
Set<Resource> allResources = new HashSet<Resource>();
for (DocumentRef docRef : sources) {
DocumentModel doc = session.getDocument(docRef);
Map<String, Object> context = Collections.<String, Object> singletonMap(
ResourceAdapter.CORE_SESSION_CONTEXT_KEY, session);
Set<Resource> resources = relManager.getAllResources(doc, context);
docResources.put(docRef, resources);
allResources.addAll(resources);
for (Resource resource : resources) {
statements.addAll(getMatchingStatements(graph, resource));
}
}
Map<String, String> namespaces = graph.getNamespaces();
// filter duplicate statements + statements involving external
// resources
IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, statements);
Graph memoryGraph = graphHelper.getGraph();
List<Statement> toRemove = new ArrayList<Statement>();
if (getBooleanProperty(IORelationAdapterProperties.IGNORE_EXTERNAL)) {
for (Statement stmt : memoryGraph.getStatements()) {
Subject subject = stmt.getSubject();
if (subject.isQNameResource()) {
if (!allResources.contains(subject)) {
toRemove.add(stmt);
continue;
}
}
Node object = stmt.getObject();
if (object.isQNameResource()) {
if (!allResources.contains(subject)) {
toRemove.add(stmt);
continue;
}
}
}
}
memoryGraph.remove(toRemove);
return new IORelationResources(namespaces, docResources, memoryGraph.getStatements());
}
}
@Override
public void getResourcesAsXML(OutputStream out, IOResources resources) {
if (!(resources instanceof IORelationResources)) {
return;
}
IORelationResources relResources = (IORelationResources) resources;
Map<String, String> namespaces = relResources.getNamespaces();
List<Statement> statements = relResources.getStatements();
IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, statements);
graphHelper.write(out);
}
private void addResourceEntry(RelationManager relManager, Map<DocumentRef, Set<Resource>> map, Node node) {
if (!node.isQNameResource()) {
return;
}
QNameResource resource = (QNameResource) node;
DocumentRef docRef = getDocumentRef(relManager, resource);
if (docRef == null) {
return;
}
if (map.containsKey(docRef)) {
map.get(docRef).add(resource);
} else {
Set<Resource> set = new HashSet<Resource>();
set.add(resource);
map.put(docRef, set);
}
}
@Override
public IOResources loadResourcesFromXML(InputStream in) {
RelationManager relManager = getRelationManager();
String graphName = getStringProperty(IORelationAdapterProperties.IMPORT_GRAPH);
if (graphName == null) {
graphName = getStringProperty(IORelationAdapterProperties.GRAPH);
}
// XXX find target graph to retrieve namespaces
Map<String, String> namespaces = null;
if (graphName != null) {
Graph graph = relManager.getGraphByName(graphName);
if (graph != null) {
namespaces = graph.getNamespaces();
}
}
IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, null);
graphHelper.read(in);
// find documents related to given statements
List<Statement> statements = filterMatchingStatements(graphHelper.getStatements());
Map<DocumentRef, Set<Resource>> docResources = new HashMap<DocumentRef, Set<Resource>>();
for (Statement statement : statements) {
Subject subject = statement.getSubject();
addResourceEntry(relManager, docResources, subject);
Node object = statement.getObject();
addResourceEntry(relManager, docResources, object);
}
return new IORelationResources(namespaces, docResources, statements);
}
@Override
public void storeResources(IOResources resources) {
if (!(resources instanceof IORelationResources)) {
return;
}
IORelationResources relResources = (IORelationResources) resources;
String graphName = getStringProperty(IORelationAdapterProperties.IMPORT_GRAPH);
if (graphName == null) {
graphName = getStringProperty(IORelationAdapterProperties.GRAPH);
}
if (graphName == null) {
log.error("Cannot find graph name");
return;
}
RelationManager relManager = getRelationManager();
Graph graph = relManager.getGraphByName(graphName);
if (graph == null) {
log.error("Cannot find graph with name " + graphName);
return;
}
graph.add(relResources.getStatements());
}
protected static Statement updateDate(Statement statement, Literal newDate, List<Resource> properties) {
for (Resource property : properties) {
// do not update if not present
if (statement.getProperty(property) != null) {
statement.setProperty(property, newDate);
}
}
return statement;
}
@Override
public IOResources translateResources(String repo, IOResources resources, DocumentTranslationMap map) {
if (map == null) {
return null;
}
if (!(resources instanceof IORelationResources)) {
return resources;
}
try (CoreSession session = CoreInstance.openCoreSessionSystem(repo)) {
IORelationResources relResources = (IORelationResources) resources;
Map<String, String> namespaces = relResources.getNamespaces();
IORelationGraphHelper graphHelper = new IORelationGraphHelper(namespaces, relResources.getStatements());
Graph graph = graphHelper.getGraph();
RelationManager relManager = getRelationManager();
// variables for date update
Literal newDate = RelationDate.getLiteralDate(new Date());
String[] dateUris = getStringArrayProperty(IORelationAdapterProperties.UPDATE_DATE_METADATA);
List<Resource> dateProperties = new ArrayList<Resource>();
if (dateUris != null) {
for (String dateUri : dateUris) {
dateProperties.add(new ResourceImpl(dateUri));
}
}
for (Map.Entry<DocumentRef, Set<Resource>> entry : relResources.getResourcesMap().entrySet()) {
DocumentRef oldRef = entry.getKey();
DocumentRef newRef = map.getDocRefMap().get(oldRef);
Set<Resource> docResources = relResources.getDocumentResources(oldRef);
for (Resource resource : docResources) {
if (!resource.isQNameResource() || oldRef.equals(newRef)) {
// cannot translate or no change => keep same
continue;
}
Statement pattern = new StatementImpl(resource, null, null);
List<Statement> outgoing = graph.getStatements(pattern);
pattern = new StatementImpl(null, null, resource);
List<Statement> incoming = graph.getStatements(pattern);
// remove old statements
graph.remove(outgoing);
graph.remove(incoming);
if (newRef == null) {
// do not replace
continue;
}
DocumentModel newDoc;
try {
newDoc = session.getDocument(newRef);
} catch (DocumentNotFoundException e) {
// do not replace
continue;
}
QNameResource qnameRes = (QNameResource) resource;
Map<String, Object> context = Collections.<String, Object> singletonMap(
ResourceAdapter.CORE_SESSION_CONTEXT_KEY, session);
Resource newResource = relManager.getResource(qnameRes.getNamespace(), newDoc, context);
Statement newStatement;
List<Statement> newOutgoing = new ArrayList<Statement>();
for (Statement stmt : outgoing) {
newStatement = (Statement) stmt.clone();
newStatement.setSubject(newResource);
if (dateProperties != null) {
newStatement = updateDate(newStatement, newDate, dateProperties);
}
newOutgoing.add(newStatement);
}
graph.add(newOutgoing);
List<Statement> newIncoming = new ArrayList<Statement>();
for (Statement stmt : incoming) {
newStatement = (Statement) stmt.clone();
newStatement.setObject(newResource);
if (dateProperties != null) {
newStatement = updateDate(newStatement, newDate, dateProperties);
}
newIncoming.add(newStatement);
}
graph.add(newIncoming);
}
}
return new IORelationResources(namespaces, relResources.getResourcesMap(), graph.getStatements());
}
}
}