org.ecoinformatics.seek.sms.AnnotationEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.ecoinformatics.seek.sms.AnnotationEngine.java

Source

/*
 * Copyright (c) 2004-2010 The Regents of the University of California.
 * All rights reserved.
 *
 * '$Author: barseghian $'
 * '$Date: 2011-01-27 17:55:42 -0800 (Thu, 27 Jan 2011) $' 
 * '$Revision: 26857 $'
 * 
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the above
 * copyright notice and the following two paragraphs appear in all copies
 * of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
 * ENHANCEMENTS, OR MODIFICATIONS.
 *
 */

package org.ecoinformatics.seek.sms;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kepler.util.DotKeplerManager;
import org.kepler.util.StaticResources;

import ptolemy.kernel.ComponentEntity;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.util.Attribute;
import ptolemy.kernel.util.ChangeRequest;
import ptolemy.kernel.util.NamedObj;
import ptolemy.kernel.util.Workspace;
import ptolemy.moml.EntityLibrary;
import ptolemy.moml.MoMLChangeRequest;
import ptolemy.vergil.tree.EntityTreeModel;
import ptolemy.vergil.tree.VisibleTreeModel;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.tidy.Checker;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdql.Query;
import com.hp.hpl.jena.rdql.QueryEngine;
import com.hp.hpl.jena.rdql.QueryResults;
import com.hp.hpl.jena.rdql.ResultBinding;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;

/**
 * AnnotationEngine Interface { AnnotationEngine instance() Vector
 * getDefaultConceptNames() void addActorAnnotation(String lsid, String
 * conceptName) EntityTreeModel buildDefaultActorLibrary() Vector search(String
 * classname) Vector search(String classname, boolean approx) }
 * 
 *@author berkley
 *@created February 17, 2005
 */
public class AnnotationEngine {
    private static final Log log = LogFactory.getLog(AnnotationEngine.class.getName());
    private static final boolean isDebugging = log.isDebugEnabled();

    private boolean _debug = false;
    // set to false to suppress debug messages
    private static AnnotationEngine _engine = null;
    // singleton instance
    private KeplerLocalLSIDService _libService = KeplerLocalLSIDService.instance();

    // should be a listener interface
    private EntityTreeModel _currentTreeModel = null;

    // paths to necessary files
    // TODO: make these more robust ...
    private String KEPLER = System.getProperty("KEPLER");
    //TODO FIXME hardcoded path:
    private String LOCAL_PATH = KEPLER + "/common/configs/" + StaticResources.RESOURCEBUNDLE_DIR + "/";
    private String ONTO_FILE = LOCAL_PATH + "ontology.owl";
    private String SCHEMA_FILE = LOCAL_PATH + "annotation-schema.owl";
    // private String ANNOTATION_FILE = LOCAL_PATH + "annotations.owl";
    private static final String ANNOTATION_FILE = new File(
            DotKeplerManager.getInstance().getTransientModuleDirectory("sms"), "annotations").toString();

    // constants to hold namespaces
    private String SCHEMA_NS = "http://seek.ecoinformatics.org/annotation-schema#";
    private String ONTO_NS = "http://seek.ecoinformatics.org/ontology#";
    // private String ONTO_NS = "urn:lsid:localhost:ontology:1:1#";

    // the annotation model
    private OntModel _ontModelAnnotations;
    // the annotation model
    private OntModel _ontModelAnnotationSchema;

    /**
     * Constructor
     */
    protected AnnotationEngine() {
        initialize();
    }

    /**
     *@return The unique instance of this class This must be called to
     *         create/obtain an instance of the engine.
     */
    public static AnnotationEngine instance() {
        if (_engine == null) {
            _engine = new AnnotationEngine();
        }
        return _engine;
    }

    /**
     * For testing... dumps out the annotations using n3 syntax.
     */
    public void print() {
        // N3 also works
        _ontModelAnnotations.write(System.out, "N-TRIPLE");
    }

    /**
     * Reload the ontology and annotation information. Eventually, should check
     * if changes were made prior to loading.
     */
    protected void initialize() {
        try {

            Checker validate = new Checker(false);
            debug("loading ontologies and annotations ... ", false);
            // create and read the annotation schema and annotations
            _ontModelAnnotationSchema = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM_RDFS_INF, null);
            _ontModelAnnotationSchema.read("file:" + SCHEMA_FILE);
            // _ontModelAnnotations.read("file:" + ANNOTATION_FILE);
            _ontModelAnnotations = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM_RDFS_INF, null);
            // _ontModelAnnotations.read("File:" + ANNOTATION_FILE);
            debug("OK", true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Not Implemented.
     * 
     *@return The defaultConceptNames value
     */
    public Vector<String> getDefaultConceptNames() {
        Iterator iter = ontModelOntology().listClasses();
        Vector<String> result = new Vector<String>();
        while (iter.hasNext()) {
            OntClass c = (OntClass) iter.next();
            result.add(c.getLocalName());
        }
        Collections.sort(result);
        return result;
    }

    /**
     * Annotate an actor (via the lsid) with a given ontology concept id
     * 
     *@param lsid
     *            The feature to be added to the ActorAnnotation attribute
     *@param conceptName
     *            The feature to be added to the ActorAnnotation attribute
     *@exception Exception
     *                Description of the Exception
     */
    public void addActorAnnotation(String lsid, String conceptName) throws Exception {
        // TODO: this should change with new schema !!!

        // check if the lsid/actor exists
        if (!_libService.isAssignedLSID(lsid)) {
            throw new Exception("Id not registered: " + lsid);
        }

        // get the class with the conceptName
        OntClass conceptClass = ontModelOntology().getOntClass(ONTO_NS + conceptName);
        if (conceptClass == null) {
            throw new Exception("Not a valid ontology concept: " + conceptName);
        }

        // make sure we don't already have the same annotation; if so,
        // clean-return
        if (annotationExists(lsid, conceptClass)) {
            return;
        }

        // construct an anonymous Actor instance, and assign an lsid property to
        // it
        OntClass actorClass = _ontModelAnnotationSchema.getOntClass(SCHEMA_NS + "Actor");
        Individual actorInst = _ontModelAnnotations.createIndividual(actorClass);
        Property lsidProp = _ontModelAnnotationSchema.getOntProperty(SCHEMA_NS + "lsid");
        actorInst.addProperty(lsidProp, lsid);

        // construct an anonymous ItemTag instance, and assign taggedItem ->
        // Actor and the concept
        OntClass itemTagClass = _ontModelAnnotationSchema.getOntClass(SCHEMA_NS + "ItemTag");
        Individual itemTagInst = _ontModelAnnotations.createIndividual(conceptClass);
        itemTagInst.addRDFType(conceptClass);
        Property taggedItemProp = _ontModelAnnotationSchema.getOntProperty(SCHEMA_NS + "taggedItem");
        itemTagInst.addProperty(taggedItemProp, actorInst);

        // construct the Annotation and assign annotates -> ItemTag
        OntClass annotClass = _ontModelAnnotationSchema.getOntClass(SCHEMA_NS + "Annotation");
        Individual annotInst = _ontModelAnnotations.createIndividual(annotClass);
        Property annotatesProp = _ontModelAnnotationSchema.getProperty(SCHEMA_NS + "annotates");
        annotInst.addProperty(annotatesProp, itemTagInst);

        // output addition
        debug("Saving Annotation...", false);
        File file = new File(ANNOTATION_FILE);
        // first, write the xml header:
        FileWriter writer = new FileWriter(file);
        writer.write("<?xml version=\"1.0\"?>\n");
        writer.close();
        // now open the stream for append
        OutputStream output = new FileOutputStream(file, true);
        _ontModelAnnotations.write(output, "RDF/XML-ABBREV");
        output.close();
        // need to update the library
        debug("OK", true);

        // rebuild the library
        NamedObj obj = _libService.getData(lsid);
        rebuildActorLibrary(obj, conceptClass.getLabel(null));
    }

    /**
     * helper function
     * 
     *@param obj
     *            Description of the Parameter
     *@param conceptLabel
     *            Description of the Parameter
     */
    private void rebuildActorLibrary(NamedObj obj, String conceptLabel) {
        // iterate through the tree to the node with the name of the
        // conceptClass

        if (conceptLabel == null) {
            return;
        }

        addToCurrentTreeModel((NamedObj) _currentTreeModel.getRoot(), obj, conceptLabel);
    }

    /**
     * recursive helper function
     * 
     *@param parent
     *            The feature to be added to the ToCurrentTreeModel attribute
     *@param obj
     *            The feature to be added to the ToCurrentTreeModel attribute
     *@param conceptLabel
     *            The feature to be added to the ToCurrentTreeModel attribute
     */
    private void addToCurrentTreeModel(NamedObj parent, NamedObj obj, String conceptLabel) {
        // return if parent is null or not a composite entity
        if (parent == null || !(parent instanceof CompositeEntity)) {
            return;
        }
        CompositeEntity folder = (CompositeEntity) parent;
        // see if parent matches concept label and add, otherwise iterate
        if (folder.getName().equals(conceptLabel)) {
            // add and do issue change request
            try {
                NamedObj obj2 = (NamedObj) obj.clone(folder.workspace());
                if (obj2 instanceof ComponentEntity) {
                    ((ComponentEntity) obj2).setContainer(folder);
                } else if (obj2 instanceof Attribute) {
                    ((Attribute) obj2).setContainer(folder);
                } else {
                    return;
                }
                ChangeRequest request = new MoMLChangeRequest(obj2, "adding object to actor library");
                obj2.requestChange(request);
                obj2.executeChangeRequests();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            // get children and iterate
            Iterator iter = folder.entityList().iterator();
            while (iter.hasNext()) {
                addToCurrentTreeModel((NamedObj) iter.next(), obj, conceptLabel);
            }
        }
    }

    /**
     * helper function. returns true if an annotation of actor with lsid and of
     * type conceptname exists
     * 
     *@param lsid
     *            Description of the Parameter
     *@param conceptClass
     *            Description of the Parameter
     *@return Description of the Return Value
     */
    private boolean annotationExists(String lsid, OntClass conceptClass) {
        try {
            Vector resultIds = new Vector();
            String str = "";
            str += "select ?item \n";
            str += "where  (?res, " + "<" + RDF.type + ">, " + "<" + conceptClass.getURI() + ">), \n";
            str += "       (?res, " + "<" + SCHEMA_NS + "taggedItem>, ?item), \n";
            str += "       (?item, " + "<" + SCHEMA_NS + "lsid>, '" + lsid + "')";
            Query q = new Query(str);
            q.setSource(_ontModelAnnotations);
            QueryResults results = new QueryEngine(q).exec();
            return results.hasNext();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Constructs the default actor library that is loaded, e.g., when Kepler is
     * started in graph mode.
     * 
     *@param _libPane
     *            Description of the Parameter
     *@return Description of the Return Value
     */
    public EntityTreeModel buildDefaultActorLibrary() {
        EntityTreeModel libraryModel = _libService.getBasicActorLibrary();
        try {
            debug("building tree model ... ", false);
            EntityLibrary root = new EntityLibrary();
            OntClass thing = ontModelOntology().getOntClass(OWL.Thing.getURI());
            Iterator iter = thing.listSubClasses(true);
            // true means direct subclasses
            while (iter.hasNext()) {
                buildTreeModel(root, (OntClass) iter.next());
            }
            debug("OK", true);
            _currentTreeModel = new VisibleTreeModel(root);
            return _currentTreeModel;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * recursive helper function to build the new tree model
     * 
     *@param parent
     *            Description of the Parameter
     *@param currClass
     *            Description of the Parameter
     */
    private void buildTreeModel(EntityLibrary parent, OntClass currClass) {
        try {
            EntityLibrary folder = new EntityLibrary();
            Workspace workspace = folder.workspace();

            // check to make sure it isn't some non-ontology class TODO:
            // change this to hold a list of target namespaces obtained
            // from loading the ontology
            if (!currClass.getNameSpace().equals(ONTO_NS)) {
                return;
            }

            folder.setName(currClass.getLabel(null));
            folder.setContainer(parent);

            // get all annotated item instances of the class
            Iterator iter = getMatchingAnnotatedItemIds(currClass).iterator();

            // for each id add the item with the same id (nested loop; ugh!)
            while (iter.hasNext()) {
                Object id = iter.next();
                NamedObj obj = _libService.getData(id.toString());
                if (obj != null) {
                    // clone into current workspace
                    NamedObj obj2 = (NamedObj) obj.clone(workspace);
                    if (obj2 instanceof ComponentEntity) {
                        ((ComponentEntity) obj2).setContainer(folder);
                    } else if (obj2 instanceof Attribute) {
                        ((Attribute) obj2).setContainer(folder);
                    }
                }
            }
            // get direct subclasses
            iter = currClass.listSubClasses(true);
            while (iter.hasNext()) {
                OntClass c = (OntClass) iter.next();
                buildTreeModel(folder, c);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * helper function. returns a list of id strings that are instances of the
     * currClass uses a rdql query.
     * 
     *@param currClass
     *            Description of the Parameter
     *@return The matchingAnnotatedItemIds value
     */
    private Vector<Object> getMatchingAnnotatedItemIds(OntClass currClass) {
        try {
            Vector<Object> resultIds = new Vector<Object>();
            String str = "";
            str += "select ?lsid \n";
            str += "where  (?res, " + "<" + RDF.type + ">, " + "<" + currClass.getURI() + ">), \n";
            str += "       (?res, " + "<" + SCHEMA_NS + "taggedItem>, ?item), \n";
            str += "       (?item, " + "<" + SCHEMA_NS + "lsid>, ?lsid)";
            Query q = new Query(str);
            q.setSource(_ontModelAnnotations);
            QueryResults results = new QueryEngine(q).exec();
            while (results.hasNext()) {
                ResultBinding res = (ResultBinding) results.next();
                // Return from get is null if not found
                Object obj = res.get("lsid");
                // obj will be a Jena object: resource, property or RDFNode.
                if (obj != null) {
                    resultIds.add(obj);
                }
            }
            results.close();
            return resultIds;
        } catch (Exception e) {
            e.printStackTrace();
            return new Vector<Object>();
        }
    }

    /**
     * for testing
     * 
     *@param str
     *            Description of the Parameter
     *@param newline
     *            Description of the Parameter
     */
    private void debug(String str, boolean newline) {
        if (!_debug) {
            return;
        }
        if (newline) {
            System.out.println(str);
        } else {
            System.out.print("ANNOTATION ENGINE: " + str);
        }
    }

    /**
     * search default case
     * 
     *@param classname
     *            Description of the Parameter
     *@return Description of the Return Value
     */
    public Vector<NamedObj> search(String classname) {
        return search(classname, true);
    }

    /**
     *@param classname
     *            the classname (as a term/keyword) to search for
     *@param approx
     *            if true, find approximate term matches, and if false, do exact
     *            matches Searches the ontology matching the term and for
     *            matches, finds annotated items. For example,
     *            search("RichnessIndex") finds all actors that instantiate
     *            either the class named "RichnessIndex" or one of its
     *            subclasses.
     *@return Description of the Return Value
     */
    public Vector<NamedObj> search(String classname, boolean approx) {
        if (isDebugging)
            log.debug("search(" + classname + ", " + approx + ")");
        // check if valid concept name
        if (!isValidClass(classname, approx)) {
            debug("didn't find classname", true);
            return new Vector<NamedObj>();
        }

        Vector<NamedObj> result = new Vector<NamedObj>();
        // find all the matching class names and their subclasses
        Vector<OntClass> classes = getMatchingClassNames(classname, approx);
        Iterator<OntClass> iter = classes.iterator();
        while (iter.hasNext()) {
            // find the matching lsids for the class name
            OntClass cls = iter.next();
            Vector<Object> ids = getMatchingAnnotatedItemIds(cls);
            Iterator<Object> idIter = ids.iterator();
            while (idIter.hasNext()) {
                // get the associated objects
                NamedObj obj = _libService.getData(idIter.next().toString());
                if (obj != null && !result.contains(obj)) {
                    result.add(obj);
                }
                // add the results
            }
        }
        return result;
    }

    /**
     * helper function to find all class names
     * 
     *@param classname
     *            Description of the Parameter
     *@param approx
     *            Description of the Parameter
     *@return The matchingClassNames value
     */
    private Vector<OntClass> getMatchingClassNames(String classname, boolean approx) {
        Vector<OntClass> initResult = new Vector<OntClass>();
        // get all classes in ontology
        Iterator iter = ontModelOntology().listClasses();
        while (iter.hasNext()) {
            // find classes that have a similar name
            OntClass cls = (OntClass) iter.next();
            if (approx && approxMatch(cls.getLocalName(), classname)) {
                initResult.add(cls);
            } else if (!approx && classname.equals(cls.getLocalName())) {
                initResult.add(cls);
            }
        }
        Vector<OntClass> result = (Vector) initResult.clone();
        iter = initResult.iterator();
        while (iter.hasNext()) {
            // find all subclasses of direct classes
            OntClass cls = (OntClass) iter.next();
            Iterator clsIter = cls.listSubClasses(false);
            // direct = false
            while (clsIter.hasNext()) {
                OntClass subCls = (OntClass) clsIter.next();
                if (!result.contains(subCls)) {
                    result.add(subCls);
                }
            }
        }
        return result;
    }

    /**
     * helper function for search. Note that we are assuming uniformity in that
     * there is only a single namespace for the ontology, and so below, we only
     * check the fragments of the URIs of the classes in the ontology.
     * 
     * This operation is just a simple optimization pre-step.
     * 
     *@param classname
     *            Description of the Parameter
     *@param approx
     *            Description of the Parameter
     *@return The validClass value
     */
    private boolean isValidClass(String classname, boolean approx) {
        // get the classes in the onto
        Iterator iter = ontModelOntology().listClasses();
        while (iter.hasNext()) {
            OntClass c = (OntClass) iter.next();
            if (!approx && c.getLocalName().equals(classname)) {
                return true;
            }
            if (approx && approxMatch(c.getLocalName(), classname)) {
                return true;
            }
        }
        return false;
    }

    /**
     * helper function for search
     * 
     *@param val1
     *            Description of the Parameter
     *@param val2
     *            Description of the Parameter
     *@return Description of the Return Value
     */
    private boolean approxMatch(String val1, String val2) {
        val1 = val1.toLowerCase();
        val2 = val2.toLowerCase();
        if (val1.indexOf(val2) != -1 || val2.indexOf(val1) != -1) {
            return true;
        }
        return false;
    }

    private OntModel ontModelOntology() {
        OntologyCatalog catalog = OntologyCatalog.instance();
        return catalog.getDefaultOntology();
    }

    /**
     * The main program for the AnnotationEngine class for testing: prints out
     * the annotations.
     * 
     *@param args
     *            The command line arguments
     */
    public static void main(String[] args) {
        AnnotationEngine eng = AnnotationEngine.instance();
        eng.print();
    }

}