001/* 002 * Copyright (c) 2004-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-08-24 22:43:10 +0000 (Mon, 24 Aug 2015) $' 007 * '$Revision: 33629 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.ecoinformatics.seek.sms; 031 032import java.io.File; 033import java.io.FileOutputStream; 034import java.io.FileWriter; 035import java.io.OutputStream; 036import java.util.Collections; 037import java.util.Iterator; 038import java.util.Vector; 039 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042import org.kepler.util.DotKeplerManager; 043import org.kepler.util.StaticResources; 044 045import com.hp.hpl.jena.ontology.Individual; 046import com.hp.hpl.jena.ontology.OntClass; 047import com.hp.hpl.jena.ontology.OntModel; 048import com.hp.hpl.jena.ontology.OntModelSpec; 049import com.hp.hpl.jena.ontology.tidy.Checker; 050import com.hp.hpl.jena.rdf.model.ModelFactory; 051import com.hp.hpl.jena.rdf.model.Property; 052import com.hp.hpl.jena.rdql.Query; 053import com.hp.hpl.jena.rdql.QueryEngine; 054import com.hp.hpl.jena.rdql.QueryResults; 055import com.hp.hpl.jena.rdql.ResultBinding; 056import com.hp.hpl.jena.vocabulary.OWL; 057import com.hp.hpl.jena.vocabulary.RDF; 058 059import ptolemy.kernel.ComponentEntity; 060import ptolemy.kernel.CompositeEntity; 061import ptolemy.kernel.util.Attribute; 062import ptolemy.kernel.util.ChangeRequest; 063import ptolemy.kernel.util.NamedObj; 064import ptolemy.kernel.util.Workspace; 065import ptolemy.moml.EntityLibrary; 066import ptolemy.moml.MoMLChangeRequest; 067import ptolemy.vergil.tree.EntityTreeModel; 068import ptolemy.vergil.tree.VisibleTreeModel; 069 070/** 071 * AnnotationEngine Interface { AnnotationEngine instance() Vector 072 * getDefaultConceptNames() void addActorAnnotation(String lsid, String 073 * conceptName) EntityTreeModel buildDefaultActorLibrary() Vector search(String 074 * classname) Vector search(String classname, boolean approx) } 075 * 076 *@author berkley 077 *@created February 17, 2005 078 */ 079public class AnnotationEngine { 080 private static final Log log = LogFactory.getLog(AnnotationEngine.class 081 .getName()); 082 private static final boolean isDebugging = log.isDebugEnabled(); 083 084 private boolean _debug = false; 085 // set to false to suppress debug messages 086 private static AnnotationEngine _engine = null; 087 // singleton instance 088 private KeplerLocalLSIDService _libService = KeplerLocalLSIDService 089 .instance(); 090 091 // should be a listener interface 092 private EntityTreeModel _currentTreeModel = null; 093 094 // paths to necessary files 095 // TODO: make these more robust ... 096 private String KEPLER = System.getProperty("KEPLER"); 097 //TODO FIXME hardcoded path: 098 private String LOCAL_PATH = KEPLER + "/common/configs/" + StaticResources.RESOURCEBUNDLE_DIR + "/"; 099 private String ONTO_FILE = LOCAL_PATH + "ontology.owl"; 100 private String SCHEMA_FILE = LOCAL_PATH + "annotation-schema.owl"; 101 // private String ANNOTATION_FILE = LOCAL_PATH + "annotations.owl"; 102 private static final String ANNOTATION_FILE = new File(DotKeplerManager.getInstance() 103 .getTransientModuleDirectory("sms"),"annotations").toString(); 104 105 // constants to hold namespaces 106 private String SCHEMA_NS = "http://seek.ecoinformatics.org/annotation-schema#"; 107 private String ONTO_NS = "http://seek.ecoinformatics.org/ontology#"; 108 // private String ONTO_NS = "urn:lsid:localhost:ontology:1:1#"; 109 110 // the annotation model 111 private OntModel _ontModelAnnotations; 112 // the annotation model 113 private OntModel _ontModelAnnotationSchema; 114 115 /** 116 * Constructor 117 */ 118 protected AnnotationEngine() { 119 initialize(); 120 } 121 122 /** 123 *@return The unique instance of this class This must be called to 124 * create/obtain an instance of the engine. 125 */ 126 public static AnnotationEngine instance() { 127 if (_engine == null) { 128 _engine = new AnnotationEngine(); 129 } 130 return _engine; 131 } 132 133 /** 134 * For testing... dumps out the annotations using n3 syntax. 135 */ 136 public void print() { 137 // N3 also works 138 _ontModelAnnotations.write(System.out, "N-TRIPLE"); 139 } 140 141 /** 142 * Reload the ontology and annotation information. Eventually, should check 143 * if changes were made prior to loading. 144 */ 145 protected void initialize() { 146 try { 147 148 Checker validate = new Checker(false); 149 debug("loading ontologies and annotations ... ", false); 150 // create and read the annotation schema and annotations 151 _ontModelAnnotationSchema = ModelFactory.createOntologyModel( 152 OntModelSpec.OWL_MEM_RDFS_INF, null); 153 _ontModelAnnotationSchema.read("file:" + SCHEMA_FILE); 154 // _ontModelAnnotations.read("file:" + ANNOTATION_FILE); 155 _ontModelAnnotations = ModelFactory.createOntologyModel( 156 OntModelSpec.OWL_MEM_RDFS_INF, null); 157 // _ontModelAnnotations.read("File:" + ANNOTATION_FILE); 158 debug("OK", true); 159 } catch (Exception e) { 160 e.printStackTrace(); 161 } 162 } 163 164 /** 165 * Not Implemented. 166 * 167 *@return The defaultConceptNames value 168 */ 169 public Vector<String> getDefaultConceptNames() { 170 Iterator iter = ontModelOntology().listClasses(); 171 Vector<String> result = new Vector<String>(); 172 while (iter.hasNext()) { 173 OntClass c = (OntClass) iter.next(); 174 result.add(c.getLocalName()); 175 } 176 Collections.sort(result); 177 return result; 178 } 179 180 /** 181 * Annotate an actor (via the lsid) with a given ontology concept id 182 * 183 *@param lsid 184 * The feature to be added to the ActorAnnotation attribute 185 *@param conceptName 186 * The feature to be added to the ActorAnnotation attribute 187 *@exception Exception 188 * Description of the Exception 189 */ 190 public void addActorAnnotation(String lsid, String conceptName) 191 throws Exception { 192 // TODO: this should change with new schema !!! 193 194 // check if the lsid/actor exists 195 if (!_libService.isAssignedLSID(lsid)) { 196 throw new Exception("Id not registered: " + lsid); 197 } 198 199 // get the class with the conceptName 200 OntClass conceptClass = ontModelOntology().getOntClass( 201 ONTO_NS + conceptName); 202 if (conceptClass == null) { 203 throw new Exception("Not a valid ontology concept: " + conceptName); 204 } 205 206 // make sure we don't already have the same annotation; if so, 207 // clean-return 208 if (annotationExists(lsid, conceptClass)) { 209 return; 210 } 211 212 // construct an anonymous Actor instance, and assign an lsid property to 213 // it 214 OntClass actorClass = _ontModelAnnotationSchema.getOntClass(SCHEMA_NS 215 + "Actor"); 216 Individual actorInst = _ontModelAnnotations 217 .createIndividual(actorClass); 218 Property lsidProp = _ontModelAnnotationSchema.getOntProperty(SCHEMA_NS 219 + "lsid"); 220 actorInst.addProperty(lsidProp, lsid); 221 222 // construct an anonymous ItemTag instance, and assign taggedItem -> 223 // Actor and the concept 224 OntClass itemTagClass = _ontModelAnnotationSchema.getOntClass(SCHEMA_NS 225 + "ItemTag"); 226 Individual itemTagInst = _ontModelAnnotations 227 .createIndividual(conceptClass); 228 itemTagInst.addRDFType(conceptClass); 229 Property taggedItemProp = _ontModelAnnotationSchema 230 .getOntProperty(SCHEMA_NS + "taggedItem"); 231 itemTagInst.addProperty(taggedItemProp, actorInst); 232 233 // construct the Annotation and assign annotates -> ItemTag 234 OntClass annotClass = _ontModelAnnotationSchema.getOntClass(SCHEMA_NS 235 + "Annotation"); 236 Individual annotInst = _ontModelAnnotations 237 .createIndividual(annotClass); 238 Property annotatesProp = _ontModelAnnotationSchema 239 .getProperty(SCHEMA_NS + "annotates"); 240 annotInst.addProperty(annotatesProp, itemTagInst); 241 242 // output addition 243 debug("Saving Annotation...", false); 244 File file = new File(ANNOTATION_FILE); 245 // first, write the xml header: 246 FileWriter writer = new FileWriter(file); 247 writer.write("<?xml version=\"1.0\"?>\n"); 248 writer.close(); 249 // now open the stream for append 250 OutputStream output = new FileOutputStream(file, true); 251 _ontModelAnnotations.write(output, "RDF/XML-ABBREV"); 252 output.close(); 253 // need to update the library 254 debug("OK", true); 255 256 // rebuild the library 257 NamedObj obj = _libService.getData(lsid); 258 rebuildActorLibrary(obj, conceptClass.getLabel(null)); 259 } 260 261 /** 262 * helper function 263 * 264 *@param obj 265 * Description of the Parameter 266 *@param conceptLabel 267 * Description of the Parameter 268 */ 269 private void rebuildActorLibrary(NamedObj obj, String conceptLabel) { 270 // iterate through the tree to the node with the name of the 271 // conceptClass 272 273 if (conceptLabel == null) { 274 return; 275 } 276 277 addToCurrentTreeModel((NamedObj) _currentTreeModel.getRoot(), obj, 278 conceptLabel); 279 } 280 281 /** 282 * recursive helper function 283 * 284 *@param parent 285 * The feature to be added to the ToCurrentTreeModel attribute 286 *@param obj 287 * The feature to be added to the ToCurrentTreeModel attribute 288 *@param conceptLabel 289 * The feature to be added to the ToCurrentTreeModel attribute 290 */ 291 private void addToCurrentTreeModel(NamedObj parent, NamedObj obj, 292 String conceptLabel) { 293 // return if parent is null or not a composite entity 294 if (parent == null || !(parent instanceof CompositeEntity)) { 295 return; 296 } 297 CompositeEntity folder = (CompositeEntity) parent; 298 // see if parent matches concept label and add, otherwise iterate 299 if (folder.getName().equals(conceptLabel)) { 300 // add and do issue change request 301 try { 302 NamedObj obj2 = (NamedObj) obj.clone(folder.workspace()); 303 if (obj2 instanceof ComponentEntity) { 304 ((ComponentEntity) obj2).setContainer(folder); 305 } else if (obj2 instanceof Attribute) { 306 ((Attribute) obj2).setContainer(folder); 307 } else { 308 return; 309 } 310 ChangeRequest request = new MoMLChangeRequest(obj2, 311 "adding object to actor library"); 312 obj2.requestChange(request); 313 obj2.executeChangeRequests(); 314 } catch (Exception e) { 315 e.printStackTrace(); 316 } 317 } else { 318 // get children and iterate 319 Iterator iter = folder.entityList().iterator(); 320 while (iter.hasNext()) { 321 addToCurrentTreeModel((NamedObj) iter.next(), obj, conceptLabel); 322 } 323 } 324 } 325 326 /** 327 * helper function. returns true if an annotation of actor with lsid and of 328 * type conceptname exists 329 * 330 *@param lsid 331 * Description of the Parameter 332 *@param conceptClass 333 * Description of the Parameter 334 *@return Description of the Return Value 335 */ 336 private boolean annotationExists(String lsid, OntClass conceptClass) { 337 try { 338 Vector resultIds = new Vector(); 339 String str = ""; 340 str += "select ?item \n"; 341 str += "where (?res, " + "<" + RDF.type + ">, " + "<" 342 + conceptClass.getURI() + ">), \n"; 343 str += " (?res, " + "<" + SCHEMA_NS 344 + "taggedItem>, ?item), \n"; 345 str += " (?item, " + "<" + SCHEMA_NS + "lsid>, '" + lsid 346 + "')"; 347 Query q = new Query(str); 348 q.setSource(_ontModelAnnotations); 349 QueryResults results = new QueryEngine(q).exec(); 350 return results.hasNext(); 351 } catch (Exception e) { 352 e.printStackTrace(); 353 return false; 354 } 355 } 356 357 /** 358 * Constructs the default actor library that is loaded, e.g., when Kepler is 359 * started in graph mode. 360 * 361 *@param _libPane 362 * Description of the Parameter 363 *@return Description of the Return Value 364 */ 365 public EntityTreeModel buildDefaultActorLibrary() { 366 EntityTreeModel libraryModel = _libService.getBasicActorLibrary(); 367 try { 368 debug("building tree model ... ", false); 369 EntityLibrary root = new EntityLibrary(); 370 OntClass thing = ontModelOntology().getOntClass(OWL.Thing.getURI()); 371 Iterator iter = thing.listSubClasses(true); 372 // true means direct subclasses 373 while (iter.hasNext()) { 374 buildTreeModel(root, (OntClass) iter.next()); 375 } 376 debug("OK", true); 377 _currentTreeModel = new VisibleTreeModel(root); 378 return _currentTreeModel; 379 } catch (Exception e) { 380 e.printStackTrace(); 381 } 382 383 return null; 384 } 385 386 /** 387 * recursive helper function to build the new tree model 388 * 389 *@param parent 390 * Description of the Parameter 391 *@param currClass 392 * Description of the Parameter 393 */ 394 private void buildTreeModel(EntityLibrary parent, OntClass currClass) { 395 try { 396 EntityLibrary folder = new EntityLibrary(); 397 Workspace workspace = folder.workspace(); 398 399 // check to make sure it isn't some non-ontology class TODO: 400 // change this to hold a list of target namespaces obtained 401 // from loading the ontology 402 if (!currClass.getNameSpace().equals(ONTO_NS)) { 403 return; 404 } 405 406 folder.setName(currClass.getLabel(null)); 407 folder.setContainer(parent); 408 409 // get all annotated item instances of the class 410 Iterator iter = getMatchingAnnotatedItemIds(currClass).iterator(); 411 412 // for each id add the item with the same id (nested loop; ugh!) 413 while (iter.hasNext()) { 414 Object id = iter.next(); 415 NamedObj obj = _libService.getData(id.toString()); 416 if (obj != null) { 417 // clone into current workspace 418 NamedObj obj2 = (NamedObj) obj.clone(workspace); 419 if (obj2 instanceof ComponentEntity) { 420 ((ComponentEntity) obj2).setContainer(folder); 421 } else if (obj2 instanceof Attribute) { 422 ((Attribute) obj2).setContainer(folder); 423 } 424 } 425 } 426 // get direct subclasses 427 iter = currClass.listSubClasses(true); 428 while (iter.hasNext()) { 429 OntClass c = (OntClass) iter.next(); 430 buildTreeModel(folder, c); 431 } 432 433 } catch (Exception e) { 434 e.printStackTrace(); 435 } 436 } 437 438 /** 439 * helper function. returns a list of id strings that are instances of the 440 * currClass uses a rdql query. 441 * 442 *@param currClass 443 * Description of the Parameter 444 *@return The matchingAnnotatedItemIds value 445 */ 446 private Vector<Object> getMatchingAnnotatedItemIds(OntClass currClass) { 447 try { 448 Vector<Object> resultIds = new Vector<Object>(); 449 String str = ""; 450 str += "select ?lsid \n"; 451 str += "where (?res, " + "<" + RDF.type + ">, " + "<" 452 + currClass.getURI() + ">), \n"; 453 str += " (?res, " + "<" + SCHEMA_NS 454 + "taggedItem>, ?item), \n"; 455 str += " (?item, " + "<" + SCHEMA_NS + "lsid>, ?lsid)"; 456 Query q = new Query(str); 457 q.setSource(_ontModelAnnotations); 458 QueryResults results = new QueryEngine(q).exec(); 459 while (results.hasNext()) { 460 ResultBinding res = (ResultBinding) results.next(); 461 // Return from get is null if not found 462 Object obj = res.get("lsid"); 463 // obj will be a Jena object: resource, property or RDFNode. 464 if (obj != null) { 465 resultIds.add(obj); 466 } 467 } 468 results.close(); 469 return resultIds; 470 } catch (Exception e) { 471 e.printStackTrace(); 472 return new Vector<Object>(); 473 } 474 } 475 476 /** 477 * for testing 478 * 479 *@param str 480 * Description of the Parameter 481 *@param newline 482 * Description of the Parameter 483 */ 484 private void debug(String str, boolean newline) { 485 if (!_debug) { 486 return; 487 } 488 if (newline) { 489 System.out.println(str); 490 } else { 491 System.out.print("ANNOTATION ENGINE: " + str); 492 } 493 } 494 495 /** 496 * search default case 497 * 498 *@param classname 499 * Description of the Parameter 500 *@return Description of the Return Value 501 */ 502 public Vector<NamedObj> search(String classname) { 503 return search(classname, true); 504 } 505 506 /** 507 *@param classname 508 * the classname (as a term/keyword) to search for 509 *@param approx 510 * if true, find approximate term matches, and if false, do exact 511 * matches Searches the ontology matching the term and for 512 * matches, finds annotated items. For example, 513 * search("RichnessIndex") finds all actors that instantiate 514 * either the class named "RichnessIndex" or one of its 515 * subclasses. 516 *@return Description of the Return Value 517 */ 518 public Vector<NamedObj> search(String classname, boolean approx) { 519 if (isDebugging) log.debug("search("+classname+", "+approx+")"); 520 // check if valid concept name 521 if (!isValidClass(classname, approx)) { 522 debug("didn't find classname", true); 523 return new Vector<NamedObj>(); 524 } 525 526 Vector<NamedObj> result = new Vector<NamedObj>(); 527 // find all the matching class names and their subclasses 528 Vector<OntClass> classes = getMatchingClassNames(classname, approx); 529 Iterator<OntClass> iter = classes.iterator(); 530 while (iter.hasNext()) { 531 // find the matching lsids for the class name 532 OntClass cls = iter.next(); 533 Vector<Object> ids = getMatchingAnnotatedItemIds(cls); 534 Iterator<Object> idIter = ids.iterator(); 535 while (idIter.hasNext()) { 536 // get the associated objects 537 NamedObj obj = _libService.getData(idIter.next().toString()); 538 if (obj != null && !result.contains(obj)) { 539 result.add(obj); 540 } 541 // add the results 542 } 543 } 544 return result; 545 } 546 547 /** 548 * helper function to find all class names 549 * 550 *@param classname 551 * Description of the Parameter 552 *@param approx 553 * Description of the Parameter 554 *@return The matchingClassNames value 555 */ 556 private Vector<OntClass> getMatchingClassNames(String classname, boolean approx) { 557 Vector<OntClass> initResult = new Vector<OntClass>(); 558 // get all classes in ontology 559 Iterator iter = ontModelOntology().listClasses(); 560 while (iter.hasNext()) { 561 // find classes that have a similar name 562 OntClass cls = (OntClass) iter.next(); 563 if (approx && approxMatch(cls.getLocalName(), classname)) { 564 initResult.add(cls); 565 } else if (!approx && classname.equals(cls.getLocalName())) { 566 initResult.add(cls); 567 } 568 } 569 Vector<OntClass> result = (Vector) initResult.clone(); 570 iter = initResult.iterator(); 571 while (iter.hasNext()) { 572 // find all subclasses of direct classes 573 OntClass cls = (OntClass) iter.next(); 574 Iterator clsIter = cls.listSubClasses(false); 575 // direct = false 576 while (clsIter.hasNext()) { 577 OntClass subCls = (OntClass) clsIter.next(); 578 if (!result.contains(subCls)) { 579 result.add(subCls); 580 } 581 } 582 } 583 return result; 584 } 585 586 /** 587 * helper function for search. Note that we are assuming uniformity in that 588 * there is only a single namespace for the ontology, and so below, we only 589 * check the fragments of the URIs of the classes in the ontology. 590 * 591 * This operation is just a simple optimization pre-step. 592 * 593 *@param classname 594 * Description of the Parameter 595 *@param approx 596 * Description of the Parameter 597 *@return The validClass value 598 */ 599 private boolean isValidClass(String classname, boolean approx) { 600 // get the classes in the onto 601 Iterator iter = ontModelOntology().listClasses(); 602 while (iter.hasNext()) { 603 OntClass c = (OntClass) iter.next(); 604 if (!approx && c.getLocalName().equals(classname)) { 605 return true; 606 } 607 if (approx && approxMatch(c.getLocalName(), classname)) { 608 return true; 609 } 610 } 611 return false; 612 } 613 614 /** 615 * helper function for search 616 * 617 *@param val1 618 * Description of the Parameter 619 *@param val2 620 * Description of the Parameter 621 *@return Description of the Return Value 622 */ 623 private boolean approxMatch(String val1, String val2) { 624 val1 = val1.toLowerCase(); 625 val2 = val2.toLowerCase(); 626 if (val1.indexOf(val2) != -1 || val2.indexOf(val1) != -1) { 627 return true; 628 } 629 return false; 630 } 631 632 private OntModel ontModelOntology() { 633 OntologyCatalog catalog = OntologyCatalog.instance(); 634 return catalog.getDefaultOntology(); 635 } 636 637 /** 638 * The main program for the AnnotationEngine class for testing: prints out 639 * the annotations. 640 * 641 *@param args 642 * The command line arguments 643 */ 644 public static void main(String[] args) { 645 AnnotationEngine eng = AnnotationEngine.instance(); 646 eng.print(); 647 } 648 649}