001package org.biopax.paxtools.io.jena; 002 003import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; 004import com.hp.hpl.jena.ontology.*; 005import com.hp.hpl.jena.rdf.model.*; 006import com.hp.hpl.jena.util.iterator.ExtendedIterator; 007import org.apache.commons.logging.Log; 008import org.apache.commons.logging.LogFactory; 009import org.biopax.paxtools.controller.AbstractPropertyEditor; 010import org.biopax.paxtools.controller.PropertyEditor; 011import org.biopax.paxtools.io.BioPAXIOHandlerAdapter; 012import org.biopax.paxtools.model.BioPAXElement; 013import org.biopax.paxtools.model.BioPAXFactory; 014import org.biopax.paxtools.model.BioPAXLevel; 015import org.biopax.paxtools.model.Model; 016import org.biopax.paxtools.util.IllegalBioPAXArgumentException; 017 018import java.io.InputStream; 019import java.io.OutputStream; 020import java.lang.reflect.InvocationTargetException; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Set; 024 025import static org.biopax.paxtools.model.BioPAXLevel.isInBioPAXNameSpace; 026 027 028/** 029 * Provides I/O support for BioPAX models presented in OWL format using the {@link com.hp.hpl.jena} 030 * package. 031 */ 032public final class JenaIOHandler extends BioPAXIOHandlerAdapter 033{ 034// ------------------------------ FIELDS ------------------------------ 035 036 private static final OntModelSpec spec= new OntModelSpec(OntModelSpec.OWL_DL_MEM);; 037 038 039 private static final Log log = LogFactory.getLog(JenaIOHandler.class); 040 041 // ------------------------------ MAPS ------------------------------ 042 private HashMap<Object, Individual> objectToIndividualMap; 043 044 OntModel ontModel; 045 046 /** 047 * If set to true, "error-mode: strict" option will be passed to Jena reader. 048 */ 049 050 // --------------------------- CONSTRUCTORS --------------------------- 051 public JenaIOHandler() 052 { 053 this(null, null); 054 } 055 056 public JenaIOHandler(BioPAXLevel level) 057 { 058 this(level.getDefaultFactory(), level); 059 } 060 061 public JenaIOHandler(BioPAXFactory factory, BioPAXLevel level) 062 { 063 super(factory, level); 064 } 065 066 // -------------------------- OTHER METHODS -------------------------- 067 068 @Override 069 protected void resetEditorMap() 070 { 071 setEditorMap(new JenaEditorMap(this.getLevel())); 072 } 073 074 protected void init(InputStream in) 075 { 076 ontModel = readJenaModel(in); 077 } 078 079 protected Map<String, String> readNameSpaces() 080 { 081 Map<String, String> namespaces = new HashMap<String, String>(); 082 namespaces.putAll(ontModel.getNsPrefixMap()); 083 return namespaces; 084 } 085 086 087 protected void createAndBind(Model model) 088 { 089 try 090 { 091 createObjects(ontModel, model); 092 bindPropertiesToObjects(ontModel, model); 093 } 094 catch (IllegalBioPAXArgumentException e) 095 { 096 log.error(e); 097 } 098 } 099 100 /** 101 * Writes a model in OWL format using the an output stream. 102 * @param model model to be converted into OWL format 103 * @param outputStream output stream to which the model will be written 104 */ 105 public void convertToOWL(Model model, OutputStream outputStream) 106 { 107 objectToIndividualMap = new HashMap<Object, Individual>(); 108 OntModel ontModel = initializeEmptyOntModel(model); 109 createIndividuals(ontModel, model); 110 bindObjectsToProperties(ontModel, model); 111 RDFWriter writer = ontModel.getWriter("RDF/XML-ABBREV"); 112 writer.setProperty("relativeURIs", "same-document, relative, parent, absolute"); 113 String base = model.getXmlBase(); 114 if (log.isDebugEnabled()) 115 { 116 log.debug("base = " + base); 117 } 118 if(base != null && !"".equals(base)) 119 writer.setProperty("xmlbase", base); 120 writer.setProperty("showXmlDeclaration", "true"); 121 writer.write(ontModel, outputStream, base); 122 //objectToIndividualMap = null; 123 } 124 125 public OntModel readJenaModel(InputStream in) 126 { 127 OntModel ontModel = createModel(); 128// ontModel.setStrictMode(true); 129// ontModel.getReader().setProperty("error-mode", "strict"); 130 ontModel.read(in, ""); 131 //ontModel.loadImports(); 132 return ontModel; 133 } 134 135 136 private void createObjects(OntModel ontModel, Model model) 137 { 138 ExtendedIterator extendedIterator = ontModel.listIndividuals(); 139 while (extendedIterator.hasNext()) 140 { 141 Individual individual = (Individual) extendedIterator.next(); 142 OntClass ontClass = (OntClass) individual.getRDFType().as(OntClass.class); 143 createAndAdd(model, individual.getURI(), ontClass.getLocalName()); 144 } 145 } 146 147 148 private void bindPropertiesToObjects(OntModel ontModel, Model model) throws IllegalBioPAXArgumentException 149 { 150 ExtendedIterator extendedIterator = ontModel.listIndividuals(); 151 while (extendedIterator.hasNext()) 152 { 153 Individual individual = (Individual) extendedIterator.next(); 154 BioPAXElement bpe = model.getByID(individual.getURI()); 155 StmtIterator stmtIterator = individual.listProperties(); 156 while (stmtIterator.hasNext()) 157 { 158 Statement statement = (Statement) stmtIterator.next(); 159 Property predicate = statement.getPredicate(); 160 161 try 162 { 163 if (isInBioPAXNameSpace(predicate.getNameSpace())) 164 { 165 bindProperty(predicate, bpe, individual, model); 166 } else if (predicate.getLocalName().equals("comment") && predicate.getNameSpace().equals( 167 "http://www.w3.org/1999/02/22-rdf-syntax-ns#")) 168 { 169 AbstractPropertyEditor editor = getRDFCommentEditor(bpe); 170 171 OntProperty ontProperty; 172 try 173 { 174 ontProperty = ((OntProperty) predicate.as(OntProperty.class)); 175 } 176 catch (ConversionException e) 177 { 178 throw new IllegalBioPAXArgumentException( 179 "Unknown property! " + predicate + " bpe:" + bpe.getRDFId(), e); 180 } 181 checkCardinalityAndBindValue(bpe, individual, model, ontProperty, editor); 182 } else if (log.isDebugEnabled()) 183 { 184 log.debug("Skipping non-biopax statement:" + predicate); 185 } 186 187 } 188 catch (IllegalBioPAXArgumentException e) 189 { 190 191 log.error("Conversion error. " + e); 192 193 } 194 catch (IllegalAccessException ex) 195 { 196 throw new IllegalBioPAXArgumentException("Conversion failed.", ex); 197 } 198 catch (InvocationTargetException ex) 199 { 200 throw new IllegalBioPAXArgumentException("Conversion failed.", ex); 201 } 202 } 203 } 204 } 205 206 207 private void bindProperty(Property predicate, BioPAXElement bpe, Individual individual, Model model) 208 throws IllegalAccessException, InvocationTargetException 209 { 210 OntProperty ontProperty; 211 try 212 { 213 ontProperty = ((OntProperty) predicate.as(OntProperty.class)); 214 } 215 catch (ConversionException e) 216 { 217 throw new IllegalBioPAXArgumentException("Unknown property! " + predicate + " bpe:" + bpe.getRDFId(), e); 218 } 219 String localName = ontProperty.getLocalName(); 220 PropertyEditor editor = this.getEditorMap().getEditorForProperty(localName, bpe.getModelInterface()); 221 if (editor != null) 222 { 223 checkCardinalityAndBindValue(bpe, individual, model, ontProperty, editor); 224 } else 225 { 226 throw new IllegalBioPAXArgumentException( 227 "Could not locate editor! " + predicate + " element:" + bpe.getRDFId() + " property:" + 228 localName); 229 } 230 } 231 232 private void checkCardinalityAndBindValue(BioPAXElement bpe, Individual individual, Model model, 233 OntProperty ontProperty, PropertyEditor editor) 234 { 235 if (editor.isMultipleCardinality()) 236 { 237 NodeIterator nodeIterator = individual.listPropertyValues(ontProperty); 238 while (nodeIterator.hasNext()) 239 { 240 RDFNode propertyValue = (RDFNode) nodeIterator.next(); 241 bindValue(propertyValue, editor, bpe, model); 242 } 243 } else 244 { 245 RDFNode propertyValue = individual.getPropertyValue(ontProperty); 246 bindValue(propertyValue, editor, bpe, model); 247 } 248 } 249 250 private void bindValue(RDFNode propertyValue, PropertyEditor editor, BioPAXElement bpe, Model model) 251 { 252 253 String stringValue = null; 254 if (propertyValue.isResource()) 255 { 256 stringValue = ((Resource) propertyValue).getURI(); 257 258 } else if (propertyValue.isLiteral()) 259 { 260 stringValue = ((Literal) propertyValue).getString(); 261 } else 262 { 263 log.error("Unexpected state." + propertyValue + " is not a resource or literal."); 264 } 265 266 bindValue(stringValue, editor, bpe, model); 267 268 } 269 270 271// --------------------------- WRITER METHODS --------------------------- 272 273 private OntModel initializeEmptyOntModel(Model model) 274 { 275 OntModel ontModel = createModel(); 276 277 String xmlBase = model.getXmlBase(); 278 if (xmlBase == null || xmlBase.equals("")) 279 { 280 xmlBase = "http://biopax.org/paxtools#"; 281 } 282 Ontology base = ontModel.createOntology(xmlBase); 283 String uri = model.getLevel().getNameSpace(); 284 uri = uri.substring(0, uri.length() - 1); 285 if (log.isDebugEnabled()) 286 { 287 log.debug("uri = " + uri); 288 } 289 ontModel.setNsPrefixes(model.getNameSpacePrefixMap()); 290 base.addImport(ontModel.createResource(uri)); 291 ontModel.loadImports(); 292 return ontModel; 293 } 294 295 private void createIndividuals(OntModel ontModel, Model model) 296 { 297 for (BioPAXElement bp : model.getObjects()) 298 { 299 String name = bp.getModelInterface().getName(); 300 name = name.substring(name.lastIndexOf('.') + 1); 301 OntClass ontClass = ontModel.getOntClass(this.getLevel().getNameSpace() + name); 302 if (log.isTraceEnabled()) 303 { 304 log.trace("ontClass = " + ontClass); 305 } 306 307 Individual individual = ontModel.createIndividual(bp.getRDFId(), ontClass); 308 if (log.isTraceEnabled()) 309 { 310 log.trace("individual = " + individual); 311 } 312 313 objectToIndividualMap.put(bp, individual); 314 } 315 } 316 317 private void bindObjectsToProperties(OntModel ontModel, Model model) 318 { 319 for (BioPAXElement bean : model.getObjects()) 320 { 321 Set<PropertyEditor> beanEditors = this.getEditorMap().getEditorsOf(bean); 322 323 for (PropertyEditor propertyEditor : beanEditors) 324 { 325 insertStatement(propertyEditor, bean, ontModel); 326 } 327 } 328 } 329 330 /** 331 * Builds an OWL statement from the given <em>bean</em> using the <em>editor</em> and inserts it 332 * into an ontology model. 333 * @param editor editor to be used for the statement build 334 * @param bean bean which the statement is about 335 * @param ontModel ontology model into which the built statement will be inserted 336 */ 337 private void insertStatement(PropertyEditor editor, BioPAXElement bean, OntModel ontModel) 338 339 { 340 Set value = editor.getValueFromBean(bean);// value cannot be null 341 for (Object valueElement : value) 342 { 343 if (!editor.isUnknown(valueElement)) buildStatementFor(bean, editor, valueElement, ontModel); 344 } 345 } 346 347 348 private void buildStatementFor(BioPAXElement bean, PropertyEditor editor, Object value, OntModel ontModel) 349 { 350 assert (bean != null && editor != null); 351 Property property = ontModel.getProperty(this.getLevel().getNameSpace() + editor.getProperty()); 352 Individual ind = objectToIndividualMap.get(bean); 353 Class range = editor.getRange(); 354 JenaEditorMap editorMap = (JenaEditorMap) this.getEditorMap(); 355 XSDDatatype dataType = editorMap.getDataTypeFor(editor); 356 if (dataType != null) 357 { 358 ind.addProperty(property, ontModel.createTypedLiteral(value.toString(), dataType)); 359 } else 360 { 361 Individual valueInd = objectToIndividualMap.get(value); 362 if (valueInd == null) // TODO not sure about Boolean case here, but this makes tests pass... 363 { 364 throw new IllegalBioPAXArgumentException( 365 range + " : for value '" + value + "' coresponding individual value is NULL " + 366 "(objectToIndividualMap)"); 367 } 368 369 ind.addProperty(property, valueInd); 370 } 371 } 372 static OntModel createModel() 373 { 374 return ModelFactory.createOntologyModel(spec); 375 } 376} 377 378