/**
* org.bioversityinternational.model.rdf.ModelDocument
*
* Created: Sep 25, 2013 - 10:45:05 AM
*
* Copyright 2013 Bioversity International and
* the Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* The code tree likely contains a copy of the License,
* ('LICENSE'), but you may also obtain a copy 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.
**/
package org.bioversityinternational.model.rdf;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.jena.vocabulary.DCTerms;
import org.bioversityinternational.Config;
import org.bioversityinternational.model.ModelException;
import org.bioversityinternational.model.general.Language;
import org.bioversityinternational.model.rdf.skos.SKOS;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
/**
* @author rbruskiewich
*
*/
public abstract class ModelDocument {
public static final int
NONE = 0 ,
INFO = 1 ,
DEBUG = 2 ,
VERBOSE = 3 ;
public static int
_trace_level = NONE ;
public static void trace(int level) {
_trace_level = level ;
}
public static final String LABEL = "Label" ;
public static final String COMMENT = "Comment" ;
public static final String DESCRIPTION = "Description" ;
public static final String SUB_CLASS_OF = "Subclass of" ;
public static final String SUB_PROPERTY_OF = "Subproperty of" ;
public static final String MEMBER = "Member" ;
public static final String DOMAIN = "Domain" ;
public static final String RANGE = "Range" ;
protected String modelURI ;
private Model model ;
private Parser parser ;
/**
* Constructor
* @param modelURI of RDF document to load
* @param local flag is true when the modelURI points to a local file path
*
* @throws IOException - if model file cannot be accessed
* ModelException - if invalid modelURI parameter specified
*/
protected ModelDocument(String modelURI, Boolean local) throws IOException, ModelException {
if(modelURI == null) {
throw new ModelException("ModelDocument(modelURI): modelURI cannot be null!") ;
}
if(_trace_level>= ModelDocument.VERBOSE) {
System.err.println( "ModelDocument(modelURI:'"+modelURI+"')") ;
}
this.modelURI = modelURI ;
Config.init();
this.model = ModelFactory.createDefaultModel();
if(local) {
FileManager.get().readModel(this.model, this.modelURI ) ;
} else {
this.model.read(this.modelURI) ;
}
this.parser = new Parser(this.model) ;
}
/**
*
* Constructor to create a ModelDocument from
* a URI assumed to specify a remote resource
*
* @param modelURI
* @throws IOException
* @throws ModelException
*/
protected ModelDocument(String modelURI)
throws IOException, ModelException {
this(modelURI,false) ;
}
public String URI() {
return this.modelURI ;
}
public Parser parser() {
return this.parser ;
}
private class TranslatedModel extends ModelDocument {
/**
* Constructor to initialize a specific language
* translation of a given ModelDocument
* @throws IOException
* @throws ModelException
*/
protected TranslatedModel(String modelURI, Boolean local)
throws IOException, ModelException {
super(modelURI, local) ;
}
}
private String language_code ;
private String language_name ;
//private String translatedModelURI ;
private TranslatedModel translatedModel ;
/**
*
* Method
*
* @param language
*/
protected void setLanguage(String language)
throws IOException, ModelException {
this.language_code = language.trim() ;
this.language_name = Language.getLanguage(this.language_code) ;
if(this.language_name == null) {
throw new ModelException("ModelDocument.setLanguage(): language code '"+
this.language_code+"' is unknown?") ;
}
// Accessing the translated model here?
//
// OOPS! Simply adding _xx where xx is the language
// WON'T REALLY WORK WHEN THE URI IS A HASH URL.
// EITHER WE APPLY ANOTHER SCHEME (SAY
// ADD THE LANGUAGE TO THE END OF THE PATH BEFORE THE HASH)
// OR JUST DO A LANGUAGE-SENSITIVE SPARQL QUERY?)
//
// Besides, is this the best place to put things?
// Would an RDF triple database with SPARQL work better?
//
// Assumed to have the URI which is the base modelURI with the language_code appended,
// e.g. germplasmTerm_sp.rdf is the Spanish language version of germplasmTerm.rdf
// this.translatedModelURI = this.modelURI+"_"+this.language_code ;
// this.translatedModel =
// new TranslatedModel(this.translatedModelURI,true) ; // assume local for now? may not be in the future?
}
/**
*
* Method that returns the ModelDocument associated with the
* current language translation associated with the core RDF model
* (i.e. RDF property Literals encoded in the non-default language)
*
* @return
*/
protected ModelDocument translatedModel() {
return this.translatedModel ;
}
/**
*
* @param format to output file
*/
public void dumpModel(OutputStream out, String format) {
model.write(out, format) ;
}
/**
*
* @param format to output file
*/
public void dumpModel(String fileName, String format) {
FileOutputStream out;
try {
out = new FileOutputStream(new File(fileName));
dumpModel(out, format);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
* @param format to stdout
*/
public void dumpModel(String format) {
dumpModel( System.out, format ) ;
}
/**
* Default is to dump the model in N3 format on stdout
*/
public void dumpModel() {
dumpModel("N3") ;
}
/**
* Default is to dump the model in N3 format on stdout
*/
public void dumpModel(OutputStream output) {
dumpModel(output, "N3") ;
}
/**
*
* @param categoryURI
* @return
* @throws ModelException
*/
public Map<String,String> subjectsOfType(String typeURI)
throws RDFParserException {
Map<String,String> subjects = parser().subjectsOfType(typeURI) ;
return subjects ;
}
/**
*
* @return list of resources of type rdfs:Class
* @throws ModelException
*/
public Map<String,String> classes()
throws RDFParserException {
Map<String,String> classes = subjectsOfType(RDFS.Class.getURI()) ;
return classes ;
}
/**
*
* @param categoryURI
* @return list of resources of type rdf:Property
* @throws ModelException
*/
public Map<String,String> properties()
throws RDFParserException {
Map<String,String> properties = subjectsOfType(RDF.Property.getURI()) ;
return properties ;
}
/**
*
* Method to return the members of a skos:Collection, as complete URI's
*
* @param collectionURI
* @return
* @throws RDFParserException
*/
public Map<String,String> members(String collectionURI)
throws RDFParserException {
Map<String,String> members =
parser().getPropertyValues(
collectionURI,
SKOS.member.getURI(),
// looking for full URI's of resources,
// probably not Literals(?), hence language insensitive
true
) ;
return members ;
}
/**
*
* Method to compile and return a catalog Map of collections with list of members
*
* @return
* @throws RDFParserException
*/
public Map<String,String> collections() throws RDFParserException {
Map<String,String> collections = parser().subjectsOfType(SKOS.Collection.getURI()) ;
for(String uri : collections.keySet()) {
Map<String,String> members = members(uri) ;
String value = "" ;
for(String member:members.values()) {
if(value.length()>0) {
value += "|" ;
}
value += member ;
}
collections.put(uri, value) ;
}
return collections ;
}
/**
* Utility method for property Map management
* @param targetMap
* @param sourceMap
* @param label
* @return
*/
protected Map<String,String> _load(
Map<String,String> targetMap,
Map<String,String> sourceMap,
String label
) {
for(String key : sourceMap.keySet()) {
if(targetMap.containsKey(label)) {
String value = targetMap.get(label) ;
targetMap.put(label, value+"|"+sourceMap.get(key)) ; // hard coded '|' string delimiter
} else {
targetMap.put(label, sourceMap.get(key)) ;
}
}
return targetMap ;
}
/**
* Method to document general details about RDFS vocabulary
* @param descriptorURI
* @return
* @throws ModelException
*/
private Map<String,String> generalDetails(ModelDocument md, String termURI, String language) throws RDFParserException {
HashMap<String,String> details = new HashMap<String,String>() ;
_load( details, md.parser().getPropertyValues( termURI, RDFS.label.getURI(), language, false), LABEL ) ;
_load( details, md.parser().getPropertyValues( termURI, RDFS.comment.getURI(), language, false), COMMENT ) ;
_load( details, md.parser().getPropertyValues( termURI, DCTerms.description.getURI(), language, false), DESCRIPTION ) ;
// what about capturing Dublin Core Meta-Data terms here(?)
return details ;
}
/**
* Method to document rdfs:Class resource specific details
*
* @param descriptorURI
* @return
* @throws ModelException
*/
public Map<String,String> classDetails(String termURI, String language) throws RDFParserException {
Map<String,String> details = null ;
Map<String,String> parts = this.parser().parseURI( termURI ) ;
// check for special case of non-RDF: XMLSchema
String ns = parts.get(Parser.NAMESPACE) ;
String uri = parts.get(Parser.URI) ;
if(ns.equals(Vocabularies.XML_SCHEMA.getURI())) {
details = new HashMap<String,String>() ;
details.put(LABEL, uri) ; // just echo the URI for now? Not sure what else would be useful here...
} else {
ModelDocument md = lookup(ns) ;
details = generalDetails(md, uri, language) ;
_load( details, md.parser().getPropertyValues( uri, RDFS.subClassOf.getURI(), language, true), SUB_CLASS_OF ) ;
_load( details, md.members(uri), MEMBER) ;
}
return details ;
}
/**
* Method to document rdfs:Class resource specific details
* @param descriptorURI
* @return
* @throws ModelException
*/
public Map<String,String> propertyDetails(String termURI, String language) throws RDFParserException {
Map<String,String> parts = this.parser().parseURI(termURI) ;
ModelDocument md = lookup(parts.get(Parser.NAMESPACE)) ;
Map<String,String> details = generalDetails(md, parts.get(Parser.URI), language) ;
_load( details, md.parser().getPropertyValues( parts.get(Parser.URI), RDFS.subPropertyOf.getURI(), language, true), SUB_PROPERTY_OF ) ;
// return full Object Resource URI's of the RDFS domain and range properties
_load( details, md.parser().getPropertyValues( parts.get(Parser.URI), RDFS.domain.getURI(), language, true), DOMAIN ) ;
_load( details, md.parser().getPropertyValues( parts.get(Parser.URI), RDFS.range.getURI(), language, true), RANGE ) ;
return details ;
}
public void dumpSchema(String language) {
System.out.println("\nReporting RDF Schema for: "+this.modelURI);
try {
Map<String,String> cmap = classes() ;
for(String curi : cmap.keySet()) {
System.out.printf("\nClass: '%s':\n\n",curi);
Map<String,String> class_details = this.classDetails(curi, language) ;
for(String key : class_details.keySet()) {
System.out.printf( "%20s: %-255s\n", key, class_details.get(key));
}
}
System.out.println() ;
Map<String,String> pmap = properties() ;
for(String puri : pmap.keySet()) {
System.out.printf("\nProperty: '%s':\n\n",puri);
Map<String,String> property_details = this.propertyDetails(puri,language) ;
for(String key : property_details.keySet()) {
System.out.printf( "%20s: %-255s\n", key, property_details.get(key));
}
}
System.out.println() ;
} catch(RDFParserException me) {
System.err.println("Caught ModelException: " + me.getMessage());
}
}
/**
*
* Method dumpSchema with default language
*/
public void dumpSchema() {
dumpSchema(null) ;
}
/*
* Maintain a static catalog of loaded ModelDocuments
*/
private static Map<String,ModelDocument> catalog =
new HashMap<String,ModelDocument>() ;
/**
*
* @param uri
* @return
*/
public static ModelDocument lookup(String uri, String language)
throws RDFParserException {
if(_trace_level>= ModelDocument.VERBOSE) {
System.err.println( "ModelDocument.lookup("+uri+")") ;
}
if(uri==null) {
throw new RDFParserException("ModelDocument.lookup() error: null URI?") ;
}
ModelDocument md = null ;
if( ! catalog.containsKey(uri)) {
Class<ModelDocument> c = Vocabularies.lookup(uri) ;
try {
md = c.newInstance();
if(language != null) {
md.setLanguage(language) ;
}
catalog.put(uri, md) ;
} catch (IOException e) {
e.printStackTrace();
throw new RDFParserException("ModelDocument.lookup() error!") ;
} catch (ModelException e) {
e.printStackTrace();
throw new RDFParserException("ModelDocument.lookup() error!") ;
} catch (InstantiationException e) {
e.printStackTrace();
throw new RDFParserException("ModelDocument.lookup() error!") ;
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RDFParserException("ModelDocument.lookup() error!") ;
}
} else {
md = catalog.get(uri) ;
}
return md ;
}
/**
* Method with default language.
* @param uri
* @return
*/
public static ModelDocument lookup(String uri)
throws RDFParserException {
return lookup(uri,null) ;
}
}