001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojavax.bio.db.biosql;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.Method;
025import java.util.ArrayList;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.biojavax.DocRefAuthor;
032import org.biojavax.RichObjectBuilder;
033import org.biojavax.SimpleCrossRef;
034import org.biojavax.SimpleDocRef;
035import org.biojavax.SimpleNamespace;
036import org.biojavax.bio.taxa.SimpleNCBITaxon;
037import org.biojavax.ontology.SimpleComparableOntology;
038
039
040/**
041 * Takes requests for RichObjects and sees if it can load them from a Hibernate
042 * database. If it can, it returns the loaded objects. Else, it creates them
043 * and persists them, then returns them. Doesn't retain a memory map so runs
044 * a lot of Hibernate queries if used frequently, but on the plus side this
045 * makes it memory-efficient.
046 * @author Richard Holland
047 * @author David Scott
048 * @author Deepak Sheoran
049 * @since 1.5
050 */
051public class BioSQLRichObjectBuilder implements RichObjectBuilder {
052    
053    private Object session;
054    private Method createQuery;
055    private Method setParameter;
056    private Method uniqueResult;
057    private Method persist;
058    
059    /**
060     * Creates a new instance of SimpleRichObjectBuilder. The session parameter
061     * is a Hibernate Session object and must not be null. It is this session
062     * that database objects will be retrieved from/persisted to.
063     * @see <a href="http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Session.html"> org.hibernate.Session</a>
064     */
065    public BioSQLRichObjectBuilder(Object session) {
066        try {
067            // Lazy load the Session class from Hibernate.
068            Class hibernateSession = session.getClass();
069            Class realHibernateSession = Class.forName("org.hibernate.Session");
070            // Test to see if our parameter is really a Session
071            if (!realHibernateSession.isAssignableFrom(hibernateSession))
072                throw new IllegalArgumentException("Parameter must be a org.hibernate.Session object");
073            this.session = session;
074            // Lookup the createQuery and persist methods
075            this.createQuery = hibernateSession.getMethod("createQuery", new Class[]{String.class});
076            this.persist = hibernateSession.getMethod("persist", new Class[]{String.class,Object.class});
077            // Lazy load the Query class from Hibernate.
078            Class hibernateQuery = Class.forName("org.hibernate.Query");
079            // Lookup the setParameter and uniqueQuery methods
080            this.setParameter = hibernateQuery.getMethod("setParameter", new Class[]{int.class,Object.class});
081            this.uniqueResult = hibernateQuery.getMethod("uniqueResult", new Class[]{});
082        } catch (ClassNotFoundException e) {
083            throw new RuntimeException(e);
084        } catch (NoSuchMethodException e) {
085            throw new RuntimeException(e);
086        }
087    }
088    
089    /**
090     * {@inheritDoc}
091     * Attempts to look up the details of the object in the database. If it
092     * finds them it loads the object and returns it. Else, it persists the
093     * wrapper object it made to do the search with and returns that.
094     */
095    public Object buildObject(Class clazz, List paramsList) {
096        // convert the params list to remove nulls as we can't process those.
097        List ourParamsList = new ArrayList(paramsList);
098        for (Iterator i = ourParamsList.iterator(); i.hasNext(); ) 
099                if (i.next()==null) i.remove();
100        // Run the query.
101        try {
102                // Create the Hibernate query to look it up with
103                String queryText;
104                String queryType;
105                if (SimpleNamespace.class.isAssignableFrom(clazz)) {
106                        queryText = "from Namespace as ns where ns.name = ?";
107                        queryType = "Namespace";
108                } else if (SimpleComparableOntology.class.isAssignableFrom(clazz)) {
109                        queryText = "from Ontology as o where o.name = ?";
110                        queryType = "Ontology";
111                } else if (SimpleNCBITaxon.class.isAssignableFrom(clazz)) {
112                        queryText = "from Taxon as o where o.NCBITaxID = ?";
113                        queryType = "Taxon";
114                } else if (SimpleCrossRef.class.isAssignableFrom(clazz)) {
115                        queryText = "from CrossRef as cr where cr.dbname = ? and cr.accession = ? and cr.version = ?";
116                        queryType = "CrossRef";
117                } else if (SimpleDocRef.class.isAssignableFrom(clazz)) {
118                        // First check if record exists with pubmed or medline id, if provided
119                        if (ourParamsList.size() > 3) {
120                                List crossRefParams = new ArrayList();
121                                String crossRefQueryText = "from DocRef as dr where dr.crossref = (select id from CrossRef as cref where cref.dbname = ? and cref.accession = ? and cref.version = ?)";
122                                crossRefParams.add(ourParamsList.get(ourParamsList.size() - 3)); // get dbname
123                                crossRefParams.add(ourParamsList.get(ourParamsList.size() - 2)); // get accession
124                                crossRefParams.add(ourParamsList.get(ourParamsList.size() - 1)); // get version
125                                // Build the query object
126                                Object query = this.createQuery.invoke(this.session, new Object[]{crossRefQueryText});
127                                // Set the parameters
128                                for (int i = 0; i < crossRefParams.size(); i++) {
129                                        query = this.setParameter.invoke(query, new Object[]{new Integer(i), crossRefParams.get(i)});
130                                }
131                                // Get the results
132                                Object result = this.uniqueResult.invoke(query, (Object[])null);
133                                // Return the found object, if found
134                                if (result!=null) return result;
135                                // If we get here, resort to looking up by author and title and remove all references
136                                // to pubmed/medline from params list.
137                                ourParamsList.remove(ourParamsList.size() - 3);
138                                ourParamsList.remove(ourParamsList.size() - 2);
139                                ourParamsList.remove(ourParamsList.size() - 1);
140                        }
141                        queryType = "DocRef";
142                        // convert List constructor to String representation for query
143                        ourParamsList.set(0, DocRefAuthor.Tools.generateAuthorString((List)ourParamsList.get(0), true));
144                        if (ourParamsList.size()<3) {
145                                queryText = "from DocRef as cr where cr.authors = ? and cr.location = ? and cr.title is null";
146                        } else {
147                                queryText = "from DocRef as cr where cr.authors = ? and cr.location = ? and cr.title = ?";
148                        }        
149                } else throw new IllegalArgumentException("Don't know how to handle objects of type "+clazz);
150                // Build the query object
151                Object query = this.createQuery.invoke(this.session, new Object[]{queryText});
152                // Set the parameters
153                for (int i = 0; i < ourParamsList.size(); i++) {
154                        query = this.setParameter.invoke(query, new Object[]{new Integer(i), ourParamsList.get(i)});
155                }
156                // Get the results
157                Object result = this.uniqueResult.invoke(query, (Object[])null);
158                // Return the found object, if found
159                if (result!=null) return result;
160                // Create, persist and return the new object otherwise
161                else {
162                        // Load the class
163                        Class[] types = new Class[ourParamsList.size()];
164                        // Find its constructor with given params
165                        for (int i = 0; i < ourParamsList.size(); i++) {
166                                if (ourParamsList.get(i) instanceof Set) types[i] = Set.class;
167                                else if (ourParamsList.get(i) instanceof Map) types[i] = Map.class;
168                                else if (ourParamsList.get(i) instanceof List) types[i] = List.class;
169                                else types[i] = ourParamsList.get(i).getClass();
170                        }
171                        Constructor c = clazz.getConstructor(types);
172                        // Instantiate it with the parameters
173                        Object o = c.newInstance(ourParamsList.toArray());
174                        // Persist and return it.
175                        this.persist.invoke(this.session, new Object[]{queryType,o});
176                        return o;
177                }
178        } catch (IllegalArgumentException ex) {
179                throw ex;
180        } catch (Exception e) {
181                // Write a useful message explaining what we were trying to do. It will
182                // be in the form "class(param,param...)".
183                StringBuffer paramsstuff = new StringBuffer();
184                paramsstuff.append(clazz);
185                paramsstuff.append("(");
186                for (int i = 0; i < ourParamsList.size(); i++) {
187                        if (i>0) paramsstuff.append(",");
188                        paramsstuff.append(ourParamsList.get(i).getClass());
189                }
190                paramsstuff.append(")");
191                // Throw the exception with our nice message
192                throw new RuntimeException("Error while trying to call new "+paramsstuff,e);
193        }
194    }
195    
196}