001    package org.apache.ojb.odmg.oql;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.io.StringReader;
019    import java.util.Enumeration;
020    import java.util.Iterator;
021    import java.util.ListIterator;
022    import java.util.Vector;
023    import java.util.List;
024    
025    import antlr.RecognitionException;
026    import antlr.TokenStreamException;
027    import org.apache.ojb.broker.ManageableCollection;
028    import org.apache.ojb.broker.PBKey;
029    import org.apache.ojb.broker.PersistenceBroker;
030    import org.apache.ojb.broker.PersistenceBrokerFactory;
031    import org.apache.ojb.broker.accesslayer.OJBIterator;
032    import org.apache.ojb.broker.query.BetweenCriteria;
033    import org.apache.ojb.broker.query.Criteria;
034    import org.apache.ojb.broker.query.Query;
035    import org.apache.ojb.broker.query.ReportQuery;
036    import org.apache.ojb.broker.query.SelectionCriteria;
037    import org.apache.ojb.broker.util.collections.ManageableArrayList;
038    import org.apache.ojb.broker.util.configuration.Configurable;
039    import org.apache.ojb.broker.util.configuration.Configuration;
040    import org.apache.ojb.broker.util.configuration.ConfigurationException;
041    import org.apache.ojb.broker.util.logging.Logger;
042    import org.apache.ojb.broker.util.logging.LoggerFactory;
043    import org.apache.ojb.odmg.ImplementationImpl;
044    import org.apache.ojb.odmg.OdmgConfiguration;
045    import org.apache.ojb.odmg.PBCapsule;
046    import org.apache.ojb.odmg.RuntimeObject;
047    import org.apache.ojb.odmg.TransactionImpl;
048    import org.odmg.QueryInvalidException;
049    import org.odmg.Transaction;
050    
051    /**
052     * The OQL query interface implementation.
053     *
054     * @version $Id: OQLQueryImpl.java,v 1.1 2007-08-24 22:17:36 ewestfal Exp $
055     */
056    public class OQLQueryImpl implements EnhancedOQLQuery, Configurable
057    {
058        private Logger log = LoggerFactory.getLogger(OQLQueryImpl.class);
059    
060        /**
061         * holds the compiled query object
062         */
063        private Query query = null;
064        private ListIterator bindIterator = null;
065        private ImplementationImpl odmg;
066    
067        public OQLQueryImpl(ImplementationImpl odmg)
068        {
069            this.odmg = odmg;
070        }
071    
072        /**
073         * @deprecated
074         * @param pbKey
075         */
076        public OQLQueryImpl(PBKey pbKey)
077        {
078        }
079    
080    
081        /**
082         * returns the compiled query object
083         */
084        public Query getQuery()
085        {
086            return query;
087        }
088    
089        /**
090         * returns the collection type to be used to contain oql query result sets
091         */
092        protected Class getCollectionClass()
093        {
094            return odmg.getOqlCollectionClass();
095        }
096    
097        /**
098         * Bind a parameter to the query.
099         * A parameter is denoted in the query string passed to <code>create</code> by <i>$i</i>,
100         * where <i>i</i> is the rank of the parameter, beginning with 1.
101         * The parameters are set consecutively by calling this method <code>bind</code>.
102         * The <i>ith</i> variable is set by the <i>ith</i> call to the <code>bind</code> method.
103         * If any of the <i>$i</i> are not set by a call to <code>bind</code> at the point
104         * <code>execute</code> is called, <code>QueryParameterCountInvalidException</code> is thrown.
105         * The parameters must be objects, and the result is an <code>Object</code>.
106         * Objects must be used instead of primitive types (<code>Integer</code> instead
107         * of <code>int</code>) for passing the parameters.
108         * <p>
109         * If the parameter is of the wrong type,
110         * <code>QueryParameterTypeInvalidException</code> is thrown.
111         * After executing a query, the parameter list is reset.
112         * @param parameter A value to be substituted for a query parameter.
113         * @exception org.odmg.QueryParameterCountInvalidException The number of calls to
114         * <code>bind</code> has exceeded the number of parameters in the query.
115         * @exception org.odmg.QueryParameterTypeInvalidException The type of the parameter does
116         * not correspond with the type of the parameter in the query.
117         */
118        public void bind(Object parameter)
119                throws org.odmg.QueryParameterCountInvalidException, org.odmg.QueryParameterTypeInvalidException
120        {
121            try
122            {
123                SelectionCriteria crit = (SelectionCriteria) getBindIterator().next();
124                crit.bind(parameter);
125    
126                // BRJ: bind is called twice for between
127                if (crit instanceof BetweenCriteria && !crit.isBound())
128                {
129                    getBindIterator().previous();
130                }
131            }
132            catch (Exception e)
133            {
134                throw new org.odmg.QueryParameterCountInvalidException(e.getMessage());
135            }
136        }
137    
138        private Vector flatten(Criteria crit, Vector acc)
139        {
140            Enumeration e = crit.getElements();
141            while (e.hasMoreElements())
142            {
143                Object o = e.nextElement();
144                if (o instanceof Criteria)
145                {
146                    Criteria pc = (Criteria) o;
147                    flatten(pc, acc);
148                }
149                else
150                {
151                    SelectionCriteria c = (SelectionCriteria) o;
152                    // BRJ : only add bindable criteria
153                    if (!c.isBound())
154                    {
155                        acc.add(c);
156                    }
157                }
158            }
159            return acc;
160        }
161    
162        /**
163         * Create an OQL query from the string parameter.
164         * In order to execute a query, an <code>OQLQuery</code> object must be created
165         * by calling <code>Implementation.newOQLQuery</code>, then calling the
166         * <code>create</code> method with the query string.
167         * The <code>create</code> method might throw <code>QueryInvalidException</code>
168         * if the query could not be compiled properly. Some implementations may not want
169         * to compile the query before <code>execute</code> is called. In this case
170         * <code>QueryInvalidException</code> is thrown when <code>execute</code> is called.
171         * @param   queryString     An OQL query.
172         * @exception       QueryInvalidException   The query syntax is invalid.
173         */
174        public void create(String queryString) throws org.odmg.QueryInvalidException
175        {
176            create(queryString, Query.NO_START_AT_INDEX, Query.NO_END_AT_INDEX);
177        }
178    
179        public void create(String queryString, int startAtIndex, int endAtIndex) throws QueryInvalidException
180        {
181            if (log.isDebugEnabled()) log.debug("create query for query-string: " + queryString);
182            /**
183             * Check preconditions.
184             * End Index cannot be set before start index.
185             * End Index cannot equal StartAtIndex
186             */
187            if ((endAtIndex != Query.NO_END_AT_INDEX) && (endAtIndex < startAtIndex))
188            {
189                throw new QueryInvalidException("endAtIndex must be greater than startAtIndex");
190            }
191            if (((endAtIndex != Query.NO_END_AT_INDEX) && (startAtIndex != Query.NO_START_AT_INDEX))
192                    && (endAtIndex == startAtIndex))
193            {
194                throw new QueryInvalidException("endAtIndex cannot be set equal to startAtIndex");
195            }
196    
197            try
198            {
199    //                      Query query = QueryPool.getQuery(queryString);
200                // Use the OQL parser to transform a query string to a valid org.apache.ojb.broker.query object
201                Query _query;
202                StringReader reader = new StringReader(queryString);
203                OQLLexer lexer = new OQLLexer(reader);
204                OQLParser parser = new OQLParser(lexer);
205                _query = parser.buildQuery();
206                setBindIterator(flatten(_query.getCriteria(), new Vector()).listIterator());
207                _query.setStartAtIndex(startAtIndex);
208                _query.setEndAtIndex(endAtIndex);
209                setQuery(_query);
210            }
211            catch (RecognitionException e)
212            {
213                throw new QueryInvalidException(e.getMessage());
214            }
215            catch (TokenStreamException e)
216            {
217                throw new QueryInvalidException(e.getMessage());
218            }
219        }
220    
221        /**
222         * Execute the query.
223         * After executing a query, the parameter list is reset.
224         * Some implementations may throw additional exceptions that are also derived
225         * from <code>ODMGException</code>.
226         * @return  The object that represents the result of the query.
227         * The returned data, whatever its OQL type, is encapsulated into an object.
228         * For instance, when OQL returns an integer, the result is put into an
229         * <code>Integer</code> object. When OQL returns a collection (literal or object),
230         * the result is always a Java collection object of the same kind
231         * (for instance, a <code>DList</code>).
232         * @exception       org.odmg.QueryException An exception has occurred while executing the query.
233         */
234        public Object execute() throws org.odmg.QueryException
235        {
236            if (log.isDebugEnabled()) log.debug("Start execute query");
237    
238            //obtain current ODMG transaction
239            TransactionImpl tx = odmg.getTxManager().getTransaction();
240            // create PBCapsule
241            PBCapsule capsule = null;
242            ManageableCollection result = null;
243    
244            try
245            {
246                capsule = new PBCapsule(odmg.getCurrentPBKey(), tx);
247                PersistenceBroker broker = capsule.getBroker();
248    
249                // ask the broker to perfom the query.
250                // the concrete result type is configurable
251    
252                if (!(query instanceof ReportQuery))
253                {
254                    result = broker.getCollectionByQuery(this.getCollectionClass(), query);
255                    performLockingIfRequired(tx, broker, result);
256                }
257                else
258                {
259                    Iterator iter = null;
260                    result = new ManageableArrayList();
261                    iter = broker.getReportQueryIteratorByQuery(query);
262                    try
263                    {
264                        while (iter.hasNext())
265                        {
266                            Object[] res = (Object[]) iter.next();
267    
268                            if (res.length == 1)
269                            {
270                                if (res[0] != null) // skip null values
271                                {
272                                    result.ojbAdd(res[0]);
273                                }
274                            }
275                            else
276                            {
277                                // skip null tuples
278                                for (int i = 0; i < res.length; i++)
279                                {
280                                    if (res[i] != null)
281                                    {
282                                        result.ojbAdd(res);
283                                        break;
284                                    }
285                                }
286                            }
287                        }
288                    }
289                    finally
290                    {
291                        if (iter instanceof OJBIterator)
292                        {
293                            ((OJBIterator) iter).releaseDbResources();
294                        }
295                    }
296                }
297                // reset iterator to start of list so we can reuse this query
298                ListIterator it = getBindIterator();
299                while (it.hasPrevious())
300                {
301                    it.previous();
302                }
303            }
304            finally
305            {
306                if (capsule != null) capsule.destroy();
307            }
308            return result;
309        }
310    
311    
312        protected void performLockingIfRequired(
313                TransactionImpl tx,
314                PersistenceBroker broker,
315                ManageableCollection result)
316        {
317            // if tx is available and implicit locking is required,
318            // we do READ-lock all found objects
319            if ((tx != null) && tx.isImplicitLocking() && tx.isOpen())
320            {
321                // read-lock all resulting objects to the current transaction
322                Iterator iter = result.ojbIterator();
323                Object toBeLocked = null;
324                try
325                {
326                    List regList = tx.getRegistrationList();
327                    while (iter.hasNext())
328                    {
329                        toBeLocked = iter.next();
330                        RuntimeObject rt = new RuntimeObject(toBeLocked, tx, false);
331                        tx.lockAndRegister(rt, Transaction.READ, true, regList);
332                    }
333                }
334                finally
335                {
336                    tx.clearRegistrationList();
337                }
338            }
339        }
340    
341    
342        protected OdmgConfiguration getConfiguration()
343        {
344            OdmgConfiguration config = (OdmgConfiguration) PersistenceBrokerFactory.getConfigurator().getConfigurationFor(null);
345            return config;
346        }
347    
348    
349        /**
350         * Sets the query.
351         * @param query The query to set
352         */
353        private void setQuery(Query query)
354        {
355            this.query = query;
356        }
357    
358        /**
359         * Gets the bindIterator.
360         * @return Returns a ListIterator
361         */
362        protected ListIterator getBindIterator()
363        {
364            return bindIterator;
365        }
366    
367        /**
368         * Sets the bindIterator.
369         * @param bindIterator The bindIterator to set
370         */
371        private void setBindIterator(ListIterator bindIterator)
372        {
373            this.bindIterator = bindIterator;
374        }
375    
376        /**
377         * @see Configurable#configure(Configuration)
378         */
379        public void configure(Configuration pConfig) throws ConfigurationException
380        {
381        }
382    
383        public int fullSize()
384        {
385            return this.query.fullSize();
386        }
387    }