001/*
002 * BioSQLRichSequenceHandler.java
003 *
004 * Created on March 7, 2006, 3:12 PM
005 */
006
007package org.biojavax.bio.db.biosql;
008
009import java.lang.reflect.Method;
010import java.util.ArrayList;
011import java.util.Iterator;
012import java.util.List;
013
014import org.biojava.bio.BioError;
015import org.biojava.bio.BioException;
016import org.biojava.bio.symbol.Alphabet;
017import org.biojava.bio.symbol.Edit;
018import org.biojava.bio.symbol.IllegalAlphabetException;
019import org.biojava.bio.symbol.IllegalSymbolException;
020import org.biojava.bio.symbol.SimpleSymbolList;
021import org.biojava.bio.symbol.Symbol;
022import org.biojava.bio.symbol.SymbolList;
023import org.biojava.utils.ChangeVetoException;
024import org.biojavax.bio.seq.DummyRichSequenceHandler;
025import org.biojavax.bio.seq.RichSequence;
026import org.biojavax.bio.seq.SimpleRichSequence;
027import org.biojavax.bio.seq.RichLocation.Tools;
028
029/**
030 * A handler which loads sequence data from a BioSQL database, caching it where possible.
031 * Note that this data is read-only. If you want to modify it permanently, you must use
032 * BioSQLRichSequenceDB.getRichSequence() to convert the original sequence into a proper
033 * SimpleRichSequence.
034 * @author Richard Holland
035 * @author David Scott
036 * @since 1.5
037 */
038public class BioSQLRichSequenceHandler extends DummyRichSequenceHandler {
039    
040    // the Hibernate session.
041    private Object session;
042    private Method createQuery;
043    private Method setParameter;
044    private Method uniqueResult;
045    
046    /**
047     * Requires a Hibernate session to work correctly. The session parameter
048     * is a Hibernate Session object and must not be null. It is this session
049     * that database objects will be retrieved from/persisted to.
050     * @see <a href="http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Session.html"> org.hibernate.Session</a>
051     */
052    public BioSQLRichSequenceHandler(Object session) {
053        super();
054        try {
055            // Lazy load the Session class from Hibernate.
056            Class hibernateSession = session.getClass();
057            Class realHibernateSession = Class.forName("org.hibernate.Session");
058            // Test to see if our parameter is really a Session
059            if (!realHibernateSession.isAssignableFrom(hibernateSession))
060                throw new IllegalArgumentException("Parameter must be a org.hibernate.Session object");
061            this.session = session;
062            // Lookup the createQuery method
063            this.createQuery = hibernateSession.getMethod("createQuery", new Class[]{String.class});
064            // Lazy load the Query class from Hibernate.
065            Class hibernateQuery = Class.forName("org.hibernate.Query");
066            // Lookup the setParameter and uniqueQuery methods
067            this.setParameter = hibernateQuery.getMethod("setParameter", new Class[]{int.class,Object.class});
068            this.uniqueResult = hibernateQuery.getMethod("uniqueResult", new Class[]{});
069        } catch (ClassNotFoundException e) {
070            throw new RuntimeException(e);
071        } catch (NoSuchMethodException e) {
072            throw new RuntimeException(e);
073        }
074    }
075    
076    /**
077     * {@inheritDoc}
078     */
079    public void edit(RichSequence seq, Edit edit) throws IndexOutOfBoundsException, IllegalAlphabetException, ChangeVetoException {
080        if (seq instanceof SimpleRichSequence) super.edit(seq,edit);
081        throw new ChangeVetoException("Cannot modify this sequence. Convert to a SimpleRichSequence first.");
082    }
083    
084    /**
085     * {@inheritDoc}
086     */
087    public Symbol symbolAt(RichSequence seq, int index) throws IndexOutOfBoundsException {
088        if (seq instanceof SimpleRichSequence) return super.symbolAt(seq,index);
089        return this.subList(seq, index, index).symbolAt(1);
090    }
091    
092    /**
093     * {@inheritDoc}
094     */
095    public List toList(RichSequence seq) {
096        if (seq instanceof SimpleRichSequence) return super.toList(seq);
097        if (seq.length()==0) return new ArrayList(); // empty list for empty seq
098        else return this.subList(seq,1,seq.length()).toList();
099    }
100    
101    /**
102     * {@inheritDoc}
103     */
104    public String subStr(RichSequence seq, int start, int end) throws IndexOutOfBoundsException {
105        if (seq instanceof SimpleRichSequence) return super.subStr(seq,start,end);
106        if (seq.length()==0) return ""; // empty seq
107        else if (seq.getCircular()) {
108            StringBuffer result = new StringBuffer(); // place to store the resulting substring
109            int[] modLocation = Tools.modulateCircularLocation(start,end,seq.length());
110            int modStart = modLocation[0];
111            int modEnd = modLocation[1];
112            int modLength = (modEnd - modStart)+1;
113            int seqLength = seq.length();
114            if (modStart==0) modStart = seqLength;
115            if (modEnd==0) modEnd = seqLength;
116            if (modEnd>seqLength) {
117                // add it in chunks
118                int remaining = modLength;
119                int chunkSize = (seqLength-modStart)+1;
120                //   add modStart -> seqLength
121                result.append(this.seqSubString(seq, modStart, seqLength));
122                remaining -= chunkSize;
123                //   repeat add seqLength
124                while (remaining > seqLength) {
125                    chunkSize = seqLength;
126                    //   add 0 -> seqLength
127                    result.append(this.seqSubString(seq, 1, seqLength));
128                    remaining -= chunkSize;
129                }
130                //   add 0 -> remaining
131                chunkSize = remaining;
132                result.append(this.seqSubString(seq, 1, chunkSize));
133            } else {
134                //   add modStart->modEnd
135                result.append(this.seqSubString(seq, modStart, modEnd));
136            }
137            return result.toString();
138        } else {
139            return this.seqSubString(seq, start, end);
140        }
141    }
142    
143    /**
144     * {@inheritDoc}
145     */
146    public SymbolList subList(RichSequence seq, int start, int end) throws IndexOutOfBoundsException {
147        if (seq instanceof SimpleRichSequence) return super.subList(seq,start,end);
148        return this.convertToSymbolList(this.subStr(seq,start,end),seq.getAlphabet());
149    }
150    
151    /**
152     * {@inheritDoc}
153     */
154    public String seqString(RichSequence seq) {
155        if (seq instanceof SimpleRichSequence) return super.seqString(seq);
156        // load whole stringSequence property from Sequence
157        try {
158            // Build the query object
159            String queryText = "select s.stringSequence from Sequence as s where s.namespace = ? and s.name = ?";
160            Object query = this.createQuery.invoke(this.session, new Object[]{queryText});
161            // Set the parameters
162            query = this.setParameter.invoke(query, new Object[]{new Integer(0), seq.getNamespace()});
163            query = this.setParameter.invoke(query, new Object[]{new Integer(1), seq.getName()});
164            // Get the results
165            Object result = this.uniqueResult.invoke(query,(Object[]) null);
166            // Return the found object, if found - null if not.
167            return (String)result;
168        } catch (Exception e) {
169            // Throw the exception with our nice message
170            throw new RuntimeException("Error while trying to locate full sequence "+seq,e);
171        }
172    }
173    
174    private String seqSubString(RichSequence seq, int start, int end) {
175        // load whole stringSequence property from Sequence
176        try {
177            // Build the query object
178            String queryText = "select substring(s.stringSequence,?,?) from Sequence as s where s.namespace = ? and s.name = ?";
179            Object query = this.createQuery.invoke(this.session, new Object[]{queryText});
180            // Set the parameters
181            query = this.setParameter.invoke(query, new Object[]{new Integer(0), new Integer(start)});
182            query = this.setParameter.invoke(query, new Object[]{new Integer(1), new Integer((end-start)+1)});
183            query = this.setParameter.invoke(query, new Object[]{new Integer(2), seq.getNamespace()});
184            query = this.setParameter.invoke(query, new Object[]{new Integer(3), seq.getName()});
185            // Get the results
186            Object result = this.uniqueResult.invoke(query,(Object[]) null);
187            // Return the found object, if found - null if not.
188            return (String)result;
189        } catch (Exception e) {
190            // Throw the exception with our nice message
191            throw new RuntimeException("Error while trying to locate full sequence "+seq,e);
192        }
193    }
194    
195    /**
196     * {@inheritDoc}
197     */
198    public Iterator iterator(RichSequence seq) {
199        if (seq instanceof SimpleRichSequence) return super.iterator(seq);
200        return this.toList(seq).iterator();
201    }
202    
203    private SymbolList convertToSymbolList(String seq, Alphabet alpha) {
204        try {
205            return new SimpleSymbolList(alpha.getTokenization("token"), seq);
206        } catch (IllegalSymbolException e) {
207            throw new BioError("Found bad symbols in sequence string!",e);
208        } catch (BioException e) {
209            throw new BioError("Found general exception in sequence string!",e);
210        }
211    }
212}