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.io.Serializable;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Method;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031
032import org.biojava.bio.BioError;
033import org.biojava.bio.BioException;
034import org.biojava.bio.seq.Feature;
035import org.biojava.bio.seq.FeatureFilter;
036import org.biojava.bio.seq.FeatureHolder;
037import org.biojava.bio.seq.SimpleFeatureHolder;
038import org.biojava.bio.seq.db.IllegalIDException;
039import org.biojava.utils.ChangeEvent;
040import org.biojava.utils.ChangeSupport;
041import org.biojava.utils.ChangeVetoException;
042import org.biojavax.bio.db.AbstractRichSequenceDB;
043import org.biojavax.bio.db.HashRichSequenceDB;
044import org.biojavax.bio.db.RichSequenceDB;
045import org.biojavax.bio.seq.RichFeature;
046import org.biojavax.bio.seq.RichSequence;
047import org.biojavax.bio.seq.SimpleRichSequence;
048
049
050/**
051 *
052 * @author Richard Holland
053 * @author David Scott
054 * @since 1.5
055 */
056public class BioSQLRichSequenceDB extends AbstractRichSequenceDB {
057    
058    private Object session;
059    private String name;
060    
061    private Method createCriteria;
062    private Method addCriteria;
063    private Method listCriteria;
064    private Method createAlias;
065    private Method createQuery;
066    private Method setParameter;
067    private Method list;
068    private Method delete;
069    private Method saveOrUpdate;
070    private Method getIdentifier;
071    private Method load;
072    private Method evict;    
073    
074    /** Creates a new instance of BioSQLRichSequenceDB */
075    public BioSQLRichSequenceDB(Object session) {
076        this(null,session);
077    }
078    
079    /** Creates a new instance of BioSQLRichSequenceDB */
080    public BioSQLRichSequenceDB(String name, Object session) {
081        this.name = name;
082        this.session = session;
083        try {
084            // Lazy load the Session class from Hibernate.
085            Class hibernateSession = session.getClass();
086            Class realHibernateSession = Class.forName("org.hibernate.Session");
087            // Test to see if our parameter is really a Session
088            if (!realHibernateSession.isAssignableFrom(hibernateSession))
089                throw new IllegalArgumentException("Parameter must be a org.hibernate.Session object");
090            this.session = session;
091            // Lookup the createQuery method
092            this.createQuery = hibernateSession.getMethod("createQuery", new Class[]{String.class});
093            this.delete = hibernateSession.getMethod("delete", new Class[]{String.class,Object.class});
094            this.saveOrUpdate = hibernateSession.getMethod("saveOrUpdate", new Class[]{String.class,Object.class});
095            this.getIdentifier = hibernateSession.getMethod("getIdentifier", new Class[]{Object.class});
096            this.evict = hibernateSession.getMethod("evict", new Class[]{Object.class});
097            this.load = hibernateSession.getMethod("load", new Class[]{String.class,Serializable.class});
098            // Lazy load the Query class from Hibernate.
099            Class hibernateQuery = Class.forName("org.hibernate.Query");
100            // Lookup the setParameter and uniqueQuery methods
101            this.setParameter = hibernateQuery.getMethod("setParameter", new Class[]{int.class,Object.class});
102            this.list = hibernateQuery.getMethod("list", new Class[]{});
103            // Lazy load the Criteria class.
104            Class criteria = Class.forName("org.hibernate.Criteria");
105            // Lookup the critera methods
106            this.createCriteria = hibernateSession.getMethod("createCriteria", new Class[]{Class.class});
107            this.addCriteria = criteria.getMethod("add", new Class[]{Class.forName("org.hibernate.criterion.Criterion")});
108            this.listCriteria = criteria.getMethod("list", new Class[]{});
109            this.createAlias = criteria.getMethod("createAlias", new Class[]{String.class,String.class});
110        } catch (ClassNotFoundException e) {
111            throw new RuntimeException(e);
112        } catch (NoSuchMethodException e) {
113            throw new RuntimeException(e);
114        }
115    }
116    
117    public String getName() {
118        return this.name;
119    }
120    
121    public Object getHibernateSession() {
122        return this.session;
123    }
124    
125    public FeatureHolder processFeatureFilter(FeatureFilter ff) {
126        BioSQLFeatureFilter bff = BioSQLFeatureFilter.Tools.convert(ff);
127        SimpleFeatureHolder results = new SimpleFeatureHolder();
128        // Apply the filter to the db.
129        try {
130            Object criteria = this.createCriteria.invoke(this.session, new Object[]{RichFeature.class});
131            this.addCriteria.invoke(criteria, new Object[]{bff.asCriterion()});
132            Map aliases = bff.criterionAliasMap();
133            for (Iterator i = aliases.keySet().iterator(); i.hasNext(); ) {
134                String property = (String)i.next();
135                String alias = (String)aliases.get(property);
136                this.createAlias.invoke(criteria,new Object[]{property,alias});
137            }
138            List cats = (List)this.listCriteria.invoke(criteria, (Object[])null);
139            for (Iterator i = cats.iterator(); i.hasNext(); ) results.addFeature((Feature)i.next());
140        } catch (IllegalAccessException e) {
141            throw new RuntimeException(e);
142        } catch (InvocationTargetException e) {
143            throw new RuntimeException(e);
144        } catch (ChangeVetoException cve) {
145            throw new BioError("Assertion failed: couldn't modify newly created SimpleFeatureHolder",cve);
146        }
147        return results;
148    }
149    
150    public FeatureHolder filter(FeatureFilter ff) {
151        FeatureHolder fh = this.processFeatureFilter(ff);
152        // Post-process only if original filter was not a BioSQLFeatureFilter.
153        if (!(ff instanceof BioSQLFeatureFilter)) {
154            // Iterate through returned features and remove any that are not accepted.
155            SimpleFeatureHolder sfh = new SimpleFeatureHolder();
156            for (Iterator i = fh.features(); i.hasNext(); ) {
157                Feature f = (Feature)i.next();
158                try {
159                    if (ff.accept(f)) sfh.addFeature(f);
160                } catch (ChangeVetoException cve) {
161                    throw new BioError("Assertion failed: couldn't modify newly created SimpleFeatureHolder",cve);
162                }
163            }
164            fh = sfh;
165        }
166        return fh;
167    }
168    
169    public Set ids() {
170        try {
171            // Build the query object
172            String queryText = "select distinct name from Sequence";
173            Object query = this.createQuery.invoke(this.session, new Object[]{queryText});
174            // Get the results
175            List result = (List)this.list.invoke(query, (Object[])null);
176            // Return the found object, if found - null if not.
177            return new HashSet(result);
178        } catch (Exception e) {
179            // Throw the exception with our nice message
180            throw new RuntimeException("Error while trying to load all names",e);
181        }
182    }
183    
184    public RichSequence fullyLoadRichSequence(RichSequence id) throws IllegalIDException, BioException {
185        if (id instanceof SimpleRichSequence) return id;
186        try {
187            Serializable identifier = (Serializable)this.getIdentifier.invoke(this.session, new Object[]{id});
188            this.evict.invoke(this.session, new Object[]{id});
189            return (RichSequence)this.load.invoke(this.session, new Object[]{"Sequence",identifier});
190        } catch (Exception e) {
191            // Throw the exception with our nice message
192            throw new RuntimeException("Error while trying to load by id: "+id,e);
193        }
194    }
195    
196    public RichSequence getRichSequence(String id) throws IllegalIDException, BioException {
197        try {
198            // Build the query object
199            String queryText = "from Sequence where name = ?";
200            Object query = this.createQuery.invoke(this.session, new Object[]{queryText});
201            // Set the parameters
202            query = this.setParameter.invoke(query, new Object[]{new Integer(0), id});
203            // Get the results
204            List result = (List)this.list.invoke(query,(Object[]) null);
205            // If the result doesn't just have a single entry, throw an exception
206            if (result.size()==0) throw new IllegalIDException("Id not found: "+id);
207            else if (result.size()>1) throw new IllegalIDException("Multiple records found with that id - use getRichSequences: "+id);
208            // Return the found object, if found - null if not.
209            return (RichSequence)result.get(0);
210        } catch (Exception e) {
211            // Throw the exception with our nice message
212            throw new RuntimeException("Error while trying to load by id: "+id,e);
213        }
214    }
215    
216    public RichSequenceDB getRichSequences(Set ids) throws BioException, IllegalIDException {
217        return this.getRichSequences(ids,null);
218    }
219    
220    public RichSequenceDB getRichSequences(Set ids, RichSequenceDB db) throws BioException, IllegalIDException {
221        if (db==null) db = new HashRichSequenceDB();
222        try {
223            for (Iterator i = ids.iterator(); i.hasNext(); ) {
224                String id = (String)i.next();
225                // Build the query object
226                String queryText = "from Sequence where name = ?";
227                Object query = this.createQuery.invoke(this.session, new Object[]{queryText});
228                // Set the parameters
229                query = this.setParameter.invoke(query, new Object[]{new Integer(0), id});
230                // Get the results
231                List result = (List)this.list.invoke(query,(Object[]) null);
232                // If the result doesn't just have a single entry, throw an exception
233                if (result.size()==0) throw new IllegalIDException("Id not found: "+id);
234                // Add the results to the results db.
235                for (Iterator j = result.iterator(); j.hasNext(); ) db.addRichSequence((RichSequence)j.next());
236            }
237        } catch (Exception e) {
238            // Throw the exception with our nice message
239            throw new RuntimeException("Error while trying to load by ids: "+ids,e);
240        }
241        return db;
242    }
243    
244    public void removeRichSequence(String id) throws IllegalIDException, BioException, ChangeVetoException {
245        if(!hasListeners(RichSequenceDB.SEQUENCES)) {
246            this._removeRichSequence(id);
247        } else {
248            ChangeSupport changeSupport = getChangeSupport(RichSequenceDB.SEQUENCES);
249            synchronized(changeSupport) {
250                ChangeEvent ce = new ChangeEvent(
251                        this,
252                        RichSequenceDB.SEQUENCES,
253                        null,
254                        id
255                        );
256                changeSupport.firePreChangeEvent(ce);
257                this._removeRichSequence(id);
258                changeSupport.firePostChangeEvent(ce);
259            }
260        }
261    }
262    
263    private void _removeRichSequence(String id) throws IllegalIDException, BioException, ChangeVetoException {
264        try {
265            // Find the object
266            RichSequence be = this.getRichSequence(id);
267            // Get the results
268            this.delete.invoke(this.session, new Object[]{"Sequence",be});
269        } catch (Exception e) {
270            // Throw the exception with our nice message
271            throw new RuntimeException("Error while trying to delete by id: "+id,e);
272        }
273    }
274    
275    public void addRichSequence(RichSequence seq) throws IllegalIDException, BioException, ChangeVetoException {
276        if(!hasListeners(RichSequenceDB.SEQUENCES)) {
277            this._addRichSequence(seq);
278        } else {
279            ChangeSupport changeSupport = getChangeSupport(RichSequenceDB.SEQUENCES);
280            synchronized(changeSupport) {
281                ChangeEvent ce = new ChangeEvent(
282                        this,
283                        RichSequenceDB.SEQUENCES,
284                        null,
285                        seq
286                        );
287                changeSupport.firePreChangeEvent(ce);
288                this._addRichSequence(seq);
289                changeSupport.firePostChangeEvent(ce);
290            }
291        }
292    }
293    
294    private void _addRichSequence(RichSequence seq) throws IllegalIDException, BioException, ChangeVetoException {
295        try {
296            // Get the results
297            this.saveOrUpdate.invoke(this.session, new Object[]{"Sequence",seq});
298        } catch (Exception e) {
299            // Throw the exception with our nice message
300            throw new RuntimeException("Error while trying to save RichSequence with id: "+seq.getName(),e);
301        }
302    }
303    
304}