/*******************************************************************************
* 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.Random;
import java.util.Set;
import java.util.TreeSet;
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.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.Resource;
/**
* Changes the namespace for all matching uris
* @author Christopher Haines (hainesc@ctrip.ufl.edu)
*/
public class ChangeNamespace {
/**
* SLF4J Logger
*/
private static Logger log = LoggerFactory.getLogger(ChangeNamespace.class);
/**
* The model to change uris in
*/
private final JenaConnect model;
/**
* The old namespace
*/
private final String oldNamespace;
/**
* The new namespace
*/
private final String newNamespace;
/**
* The model in which to search for previously used uris
*/
private final JenaConnect vivo;
/**
* Log error messages for changed nodes
*/
private final boolean errorLogging;
/**
* Constructor
* @param args commandline arguments
* @throws IOException error creating task
* @throws UsageException user requested usage message
*/
private ChangeNamespace(String[] args) throws IOException, UsageException {
this(getParser().parse(args));
}
/**
* Constructor
* @param argList parsed argument list
* @throws IOException error reading config
*/
private ChangeNamespace(ArgList argList) throws IOException {
this(
JenaConnect.parseConfig(argList.get("i"), argList.getValueMap("I")),
JenaConnect.parseConfig(argList.get("v"), argList.getValueMap("V")),
argList.get("u"),
argList.get("n"),
argList.has("e")
);
}
/**
* Constructor
* @param model model to change uris in
* @param vivo model in which to search for previously used uris
* @param oldName old namespace
* @param newName new namespace
* @param errorLog log error messages for changed nodes
*/
public ChangeNamespace(JenaConnect model, JenaConnect vivo, String oldName, String newName, boolean errorLog) {
if(model == null) {
throw new IllegalArgumentException("No input model provided! Must provide an input model");
}
this.model = model;
if(vivo == null) {
throw new IllegalArgumentException("No vivo model provided! Must provide a vivo model");
}
this.vivo = vivo;
this.oldNamespace = oldName;
this.newNamespace = newName;
this.errorLogging = errorLog;
this.model.printParameters();
this.vivo.printParameters();
}
/**
* Gets an unused URI in the the given namespace for the given models
* @param namespace the namespace
* @param models models to check in
* @return the uri
* @throws IOException error connecting
*/
public static String getUnusedURI(String namespace, JenaConnect... models) throws IOException {
if((namespace == null) || namespace.equals("")) {
throw new IllegalArgumentException("namespace cannot be empty");
}
String uri = null;
Random random = new Random();
while(uri == null) {
uri = namespace + "n" + random.nextInt(Integer.MAX_VALUE);
log.trace("evaluating uri <" + uri + ">");
for(JenaConnect model : models) {
boolean modelContains = model.containsURI(uri);
log.trace("model <" + model.getModelName() + "> contains this uri?: " + modelContains);
if(modelContains) {
uri = null;
break;
}
}
}
log.debug("Using new URI: <" + uri + ">");
return uri;
}
/**
* Changes the namespace for all matching uris
* @param model the model to change namespaces for
* @param vivo the model to search for uris in
* @param oldNamespace the old namespace
* @param newNamespace the new namespace
* @param errorLog log error messages for changed nodes
* @throws IOException error connecting
*/
public static void changeNS(JenaConnect model, JenaConnect vivo, String oldNamespace, String newNamespace, boolean errorLog) throws IOException {
if((oldNamespace == null) || oldNamespace.trim().equals("")) {
throw new IllegalArgumentException("old namespace cannot be empty");
}
if((newNamespace == null) || newNamespace.trim().equals("")) {
throw new IllegalArgumentException("new namespace cannot be empty");
}
if(oldNamespace.trim().equals(newNamespace.trim())) {
log.trace("namespaces are equal, nothing to change");
return;
}
batchRename(model, vivo, oldNamespace.trim(), newNamespace.trim(), errorLog);
}
/**
* Rename unmatched resources from a given namespace in the given model to another (vivo) model
* @param model the model to change namespaces for
* @param vivo the model to search for uris in
* @param oldNamespace the old namespace
* @param newNamespace the new namespace
* @param errorLog log error messages for changed nodes
* @throws IOException error connecting
*/
private static void batchRename(JenaConnect model, JenaConnect vivo, String oldNamespace, String newNamespace, boolean errorLog) throws IOException {
//Grab all resources matching namespaces needing changed
String subjectQuery = "" +
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n" +
"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \n" +
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" +
"PREFIX owl: <http://www.w3.org/2002/07/owl#> \n" +
"PREFIX swrl: <http://www.w3.org/2003/11/swrl#> \n" +
"PREFIX swrlb: <http://www.w3.org/2003/11/swrlb#> \n" +
"PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#> \n" +
"PREFIX bibo: <http://purl.org/ontology/bibo/> \n" +
"PREFIX dcelem: <http://purl.org/dc/elements/1.1/> \n" +
"PREFIX dcterms: <http://purl.org/dc/terms/> \n" +
"PREFIX event: <http://purl.org/NET/c4dm/event.owl#> \n" +
"PREFIX foaf: <http://xmlns.com/foaf/0.1/> \n" +
"PREFIX geo: <http://aims.fao.org/aos/geopolitical.owl#> \n" +
"PREFIX skos: <http://www.w3.org/2004/02/skos/core#> \n" +
"PREFIX ufVivo: <http://vivo.ufl.edu/ontology/vivo-ufl/> \n" +
"PREFIX core: <http://vivoweb.org/ontology/core#> \n" +
"\n" +
"SELECT ?sub \n" +
"WHERE {\n" +
"\t" + "?sub ?p ?o . \n" +
"\t" + "FILTER regex(str(?sub), \"^" + oldNamespace + "\" ) \n" + "} ORDER BY ?sub";
log.debug("Change Query:\n" + subjectQuery);
Set<String> changeArray = new TreeSet<String>();
ResultSet results = model.executeSelectQuery(subjectQuery,true,false);
for(QuerySolution solution : IterableAdaptor.adapt(results)) {
String renameURI = solution.getResource("sub").getURI();
changeArray.add(renameURI);
}
int total = changeArray.size();
int count = 0;
for(String sub : changeArray) {
count++;
Resource res = model.getJenaModel().getResource(sub);
float percent = Math.round(10000f * count / total) / 100f;
log.trace("(" + count + "/" + total + ": " + percent + "%): Finding unused URI for resource <" + res + ">");
String uri = getUnusedURI(newNamespace, vivo, model);
if(errorLog) {
log.warn("Resource <" + res.getURI() + "> was found and renamed to new uri <" + uri + ">!");
}
RenameResources.renameResource(res, uri);
}
log.info("Changed namespace for " + changeArray.size() + " rdf nodes");
}
/**
* Change namespace
* @throws IOException error connecting
*/
public void execute() throws IOException {
changeNS(this.model, this.vivo, this.oldNamespace, this.newNamespace, this.errorLogging);
this.model.sync();
}
/**
* Get the ArgParser for this task
* @return the ArgParser
*/
private static ArgParser getParser() {
ArgParser parser = new ArgParser("ChangeNamespace");
// Inputs
parser.addArgument(new ArgDef().setShortOption('i').setLongOpt("inputModel").withParameter(true, "CONFIG_FILE").setDescription("config file for input jena model").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('I').setLongOpt("inputModelOverride").withParameterValueMap("JENA_PARAM", "VALUE").setDescription("override the JENA_PARAM of input jena model config using VALUE").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('v').setLongOpt("vivoModel").withParameter(true, "CONFIG_FILE").setDescription("config file for vivo jena model").setRequired(false));
parser.addArgument(new ArgDef().setShortOption('V').setLongOpt("vivoModelOverride").withParameterValueMap("JENA_PARAM", "VALUE").setDescription("override the JENA_PARAM of vivo jena model config using VALUE").setRequired(false));
// Params
parser.addArgument(new ArgDef().setShortOption('u').setLongOpt("oldNamespace").withParameter(true, "OLD_NAMESPACE").setDescription("The old namespace").setRequired(true));
parser.addArgument(new ArgDef().setShortOption('n').setLongOpt("newNamespace").withParameter(true, "NEW_NAMESPACE").setDescription("The new namespace").setRequired(true));
parser.addArgument(new ArgDef().setShortOption('e').setLongOpt("errorLogging").setDescription("Log error messages for each record changed").setRequired(false));
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 ChangeNamespace(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);
}
}
}
}