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 }