/*******************************************************************************
* Copyright (c) 2010-2011 VIVO Harvester Team. For full list of contributors, please see the AUTHORS file provided.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the new BSD license which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.html
******************************************************************************/
package org.vivoweb.harvester.qualify;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vivoweb.harvester.util.InitLog;
import org.vivoweb.harvester.util.IterableAdaptor;
import org.vivoweb.harvester.util.args.ArgDef;
import org.vivoweb.harvester.util.args.ArgList;
import org.vivoweb.harvester.util.args.ArgParser;
import org.vivoweb.harvester.util.args.UsageException;
import org.vivoweb.harvester.util.repo.JenaConnect;
import com.hp.hpl.jena.graph.BulkUpdateHandler;
import com.hp.hpl.jena.graph.Graph;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.reasoner.InfGraph;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.util.iterator.Filter;
/**
* Changes the namespace for all matching uris
* @author Christopher Haines (hainesc@ctrip.ufl.edu)
*/
public class RenameResources {
/**
* SLF4J Logger
*/
private static Logger log = LoggerFactory.getLogger(RenameResources.class);
/**
* The resource uri to which old uri will be renamed
*/
private String newUri;
/**
* The resource uri to be renamed to the new uri
*/
private String[] oldUris;
/**
* The jena model
*/
private JenaConnect jena;
/**
* Commandline Constructor
* @param args commandline arguments
* @throws IOException error parsing args
* @throws UsageException user requested usage message
*/
private RenameResources(String[] args) throws IOException, UsageException {
this(getParser().parse(args));
}
/**
* Arg Parser Constructor
* @param argList parsed commandling arguments
* @throws IOException error connecting to jena model
*/
private RenameResources(ArgList argList) throws IOException {
this(JenaConnect.parseConfig(argList.get("i"), argList.getValueMap("I")), argList.get("n"), argList.getAll("u").toArray(new String[]{}));
}
/**
* Constructor
* @param jena the jena model
* @param newUri resource uri to which old uri will be renamed
* @param oldUris The resource uri to be renamed to the new uri
*/
public RenameResources(JenaConnect jena, String newUri, String... oldUris) {
this.jena = jena;
this.newUri = newUri;
this.oldUris = oldUris;
if(this.jena == null) {
throw new IllegalArgumentException("Must provide a jena model");
}
}
/**
* Run the Merge
*/
public void execute() {
for(String sec : this.oldUris) {
renameResource(this.jena.getJenaModel().getResource(sec), this.newUri);
}
this.jena.sync();
}
/**
* <p>Answer a new resource that occupies the same position in the graph as the current
* resource <code>old</code>, but that has the given URI. In the process, the existing
* statements referring to <code>old</code> are removed. Since Jena does not allow the
* identity of a resource to change, this is the closest approximation to a rename operation
* that works.
* </p>
* <p><strong>Notes:</strong> This method does minimal checking, so renaming a resource
* to its own URI is unpredictable. Furthermore, it is a general and simple approach, and
* in given applications it may be possible to do this operation more efficiently. Finally,
* if <code>res</code> is a property, existing statements that use the property will not
* be renamed, nor will occurrences of <code>res</code> in other models.
* </p>
* @param old An existing resource in a given model
* @param uri A new URI for resource old, or <code>null</code> to rename old to a bNode
* @return A new resource that occupies the same position in the graph as old, but which
* has the new given URI.
* @author jena team - see com.hp.hpl.jena.util.ResourceUtils
* @author hainesc - fixed to not cause ConcurrentModificationException in TDB
*/
public static Resource renameResource(final Resource old, final String uri) {
// Let's work directly with the Graph. Faster if old is attached to a InfModel (~2 times).
// Otherwise, we would create more or less many fine grained removals and additions of
// statements on model which may cause to refresh the backing reasoner frequently, thus,
// introducing a lot of update work. With this implementation this happens at most once.
// It could be solved without the need to work directly with the graph if we had a bulk
// update feature over Model/InfModel/OntModel.
// Some tests have shown that even if there is just one triple involving the resource
// to be renamed and the model has a reasonable size (~10000 triples) then it is still
// faster (~30%) -- including the final rebind() -- than working at the level of model.
final Node resAsNode = old.asNode();
final Model model = old.getModel();
final Graph graph = model.getGraph(), rawGraph;
if(graph instanceof InfGraph)
rawGraph = ((InfGraph)graph).getRawGraph();
else
rawGraph = graph;
final Set<Triple> reflexiveTriples = new HashSet<Triple>();
// list the statements that mention old as a subject
ExtendedIterator<Triple> subjectTriples = rawGraph.find(resAsNode, null, null);
// List the statements that mention old as an object
ExtendedIterator<Triple> objectTriples = rawGraph.find(null, null, resAsNode);
// combine these iterators
ExtendedIterator<Triple> combinedTriples = subjectTriples.andThen(objectTriples);
// Filter reflexive triples, which are found twice in each find method, thus, we need to make sure to keep only one.
ExtendedIterator<Triple> filteredTriples = combinedTriples.filterKeep(new Filter<Triple>() {
@Override
public boolean accept(final Triple o) {
if(o.getSubject().equals(o.getObject())) {
reflexiveTriples.add(o);
return false;
}
return true;
}
});
// create a new resource node to replace old
final Resource newRes = model.createResource(uri);
final Node newResAsNode = newRes.asNode();
Set<Triple> removeTriples = new HashSet<Triple>();
Set<Triple> addTriples = new HashSet<Triple>();
Node subj, obj;
for(final Triple t : IterableAdaptor.adapt(filteredTriples)) {
// first, create a new triple to refer to newRes instead of old and mark it for addition
subj = (t.getSubject().equals(resAsNode)) ? newResAsNode : t.getSubject();
obj = (t.getObject().equals(resAsNode)) ? newResAsNode : t.getObject();
//rawGraph.add(Triple.create(subj, t.getPredicate(), obj));
addTriples.add(Triple.create(subj, t.getPredicate(), obj));
// second, mark existing triple for removal
//filteredTriples.remove();
removeTriples.add(t);
}
// finally, handle reflexive triples (if any)
for(final Triple t : reflexiveTriples) {
//rawGraph.delete(rt);
removeTriples.add(t);
//rawGraph.add(Triple.create(newResAsNode, rt.getPredicate(), newResAsNode));
addTriples.add(Triple.create(newResAsNode, t.getPredicate(), newResAsNode));
}
// // remove all triples marked for removal
// for(final Triple t : removeTriples) {
// rawGraph.delete(t);
// }
// // add all triples marked for addition
// for(final Triple t : addTriples) {
// rawGraph.add(t);
// }
BulkUpdateHandler buh = rawGraph.getBulkUpdateHandler();
buh.add(addTriples.iterator());
buh.delete(removeTriples.iterator());
// Did we work in the back of the InfGraph? If so, we need to rebind raw data (more or less expensive)!
if(rawGraph != graph)
((InfGraph)graph).rebind();
return newRes;
}
/**
* Get the ArgParser for this task
* @return the ArgParser
*/
private static ArgParser getParser() {
ArgParser parser = new ArgParser("RenameResources");
// Inputs
parser.addArgument(new ArgDef().setShortOption('i').setLongOpt("inputModel").withParameter(true, "CONFIG_FILE").setDescription("config file for jena model").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('I').setLongOpt("inputModelOverride").withParameterValueMap("JENA_PARAM", "VALUE").setDescription("override the JENA_PARAM of jena model config using VALUE").setRequired(false));
// Params
parser.addArgument(new ArgDef().setShortOption('n').setLongOpt("new-uri").withParameter(true, "RESOURCE_URI").setDescription("The resource uri to which old uri will be renamed").setRequired(true));
parser.addArgument(new ArgDef().setShortOption('u').setLongOpt("old-uri").withParameters(true, "RESOURCE_URI").setDescription("The resource uri to be renamed to the new uri").setRequired(true));
return parser;
}
/**
* Main method
* @param args commandline arguments
*/
public static void main(String... args) {
Exception error = null;
try {
InitLog.initLogger(args, getParser());
log.info(getParser().getAppName() + ": Start");
new RenameResources(args).execute();
} catch(IllegalArgumentException e) {
log.error(e.getMessage());
log.debug("Stacktrace:", e);
System.out.println(getParser().getUsage());
error = e;
} catch(UsageException e) {
log.info("Printing Usage:");
System.out.println(getParser().getUsage());
error = e;
} catch(Exception e) {
log.error(e.getMessage());
log.debug("Stacktrace:", e);
error = e;
} finally {
log.info(getParser().getAppName() + ": End");
if(error != null) {
System.exit(1);
}
}
}
}