Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/objectstyle/cayenne/access/trans/QueryAssemblerHelper.java


1   /* ====================================================================
2    * 
3    * The ObjectStyle Group Software License, Version 1.0 
4    *
5    * Copyright (c) 2002-2003 The ObjectStyle Group 
6    * and individual authors of the software.  All rights reserved.
7    *
8    * Redistribution and use in source and binary forms, with or without
9    * modification, are permitted provided that the following conditions
10   * are met:
11   *
12   * 1. Redistributions of source code must retain the above copyright
13   *    notice, this list of conditions and the following disclaimer. 
14   *
15   * 2. Redistributions in binary form must reproduce the above copyright
16   *    notice, this list of conditions and the following disclaimer in
17   *    the documentation and/or other materials provided with the
18   *    distribution.
19   *
20   * 3. The end-user documentation included with the redistribution, if
21   *    any, must include the following acknowlegement:  
22   *       "This product includes software developed by the 
23   *        ObjectStyle Group (http://objectstyle.org/)."
24   *    Alternately, this acknowlegement may appear in the software itself,
25   *    if and wherever such third-party acknowlegements normally appear.
26   *
27   * 4. The names "ObjectStyle Group" and "Cayenne" 
28   *    must not be used to endorse or promote products derived
29   *    from this software without prior written permission. For written 
30   *    permission, please contact andrus@objectstyle.org.
31   *
32   * 5. Products derived from this software may not be called "ObjectStyle"
33   *    nor may "ObjectStyle" appear in their names without prior written
34   *    permission of the ObjectStyle Group.
35   *
36   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39   * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47   * SUCH DAMAGE.
48   * ====================================================================
49   *
50   * This software consists of voluntary contributions made by many
51   * individuals on behalf of the ObjectStyle Group.  For more
52   * information on the ObjectStyle Group, please see
53   * <http://objectstyle.org/>.
54   *
55   */
56  package org.objectstyle.cayenne.access.trans;
57  
58  import java.util.Iterator;
59  import java.util.List;
60  import java.util.Map;
61  
62  import org.apache.log4j.Logger;
63  import org.objectstyle.cayenne.CayenneRuntimeException;
64  import org.objectstyle.cayenne.DataObject;
65  import org.objectstyle.cayenne.ObjectId;
66  import org.objectstyle.cayenne.exp.Expression;
67  import org.objectstyle.cayenne.map.DbAttribute;
68  import org.objectstyle.cayenne.map.DbAttributePair;
69  import org.objectstyle.cayenne.map.DbEntity;
70  import org.objectstyle.cayenne.map.DbRelationship;
71  import org.objectstyle.cayenne.map.ObjAttribute;
72  import org.objectstyle.cayenne.map.ObjEntity;
73  import org.objectstyle.cayenne.map.ObjRelationship;
74  
75  /** 
76   * Translates parts of the query to SQL.
77   * Always works in the context of parent Translator. 
78   * 
79   * @author Andrei Adamchik
80   */
81  public abstract class QueryAssemblerHelper {
82      private static Logger logObj = Logger.getLogger(QueryAssemblerHelper.class);
83  
84      protected QueryAssembler queryAssembler;
85  
86      public QueryAssemblerHelper() {
87      }
88  
89      /** Creates QueryAssemblerHelper. Sets queryAssembler property. */
90      public QueryAssemblerHelper(QueryAssembler queryAssembler) {
91          this.queryAssembler = queryAssembler;
92      }
93  
94      /** Returns parent QueryAssembler that uses this helper. */
95      public QueryAssembler getQueryAssembler() {
96          return queryAssembler;
97      }
98  
99      public void setQueryAssembler(QueryAssembler queryAssembler) {
100         this.queryAssembler = queryAssembler;
101     }
102 
103     /** Translates the part of parent translator's query that is supported 
104      * by this PartTranslator. For example, QualifierTranslator will process 
105      * qualifier expression, OrderingTranslator - ordering of the query. 
106      * In the process of translation parent translator is notified of any 
107      * join tables added (so that it can update its "FROM" clause). 
108      * Also parent translator is consulted about table aliases to use 
109      * when translating columns. */
110     public abstract String doTranslation();
111 
112     public ObjEntity getObjEntity() {
113         return getQueryAssembler().getRootEntity();
114     }
115 
116     public DbEntity getDbEntity() {
117         return getQueryAssembler().getRootDbEntity();
118     }
119 
120     /** Processes parts of the OBJ_PATH expression. */
121     protected void appendObjPath(StringBuffer buf, Expression pathExp) {
122 
123         Iterator it = getObjEntity().resolvePathComponents(pathExp);
124         ObjRelationship lastRelationship = null;
125 
126         while (it.hasNext()) {
127             Object pathComp = it.next();
128 
129             if (pathComp instanceof ObjRelationship) {
130                 ObjRelationship rel = (ObjRelationship) pathComp;
131 
132                 // if this is a last relationship in the path,
133                 // it needs special handling
134                 if (!it.hasNext()) {
135                     processRelTermination(buf, rel);
136                 } else {
137                     // find and add joins ....
138                     Iterator relit = rel.getDbRelationships().iterator();
139                     while (relit.hasNext()) {
140                         queryAssembler.dbRelationshipAdded(
141                             (DbRelationship) relit.next());
142                     }
143                 }
144                 lastRelationship = rel;
145             } else {
146                 ObjAttribute objAttr = (ObjAttribute) pathComp;
147                 if (lastRelationship != null) {
148                     List lastDbRelList = lastRelationship.getDbRelationships();
149                     DbRelationship lastDbRel =
150                         (DbRelationship) lastDbRelList.get(
151                             lastDbRelList.size() - 1);
152                     processColumn(buf, objAttr.getDbAttribute(), lastDbRel);
153                 } else {
154                     processColumn(buf, objAttr.getDbAttribute());
155                 }
156             }
157         }
158     }
159 
160     protected void appendDbPath(StringBuffer buf, Expression pathExp) {
161         Iterator it = getDbEntity().resolvePathComponents(pathExp);
162 
163         while (it.hasNext()) {
164             Object pathComp = it.next();
165             if (pathComp instanceof DbRelationship) {
166                 DbRelationship rel = (DbRelationship) pathComp;
167 
168                 // if this is a last relationship in the path,
169                 // it needs special handling
170                 if (!it.hasNext()) {
171                     processRelTermination(buf, rel);
172                 } else {
173                     // find and add joins ....
174                     queryAssembler.dbRelationshipAdded(rel);
175                 }
176             } else {
177                 DbAttribute dbAttr = (DbAttribute) pathComp;
178                 processColumn(buf, dbAttr);
179             }
180         }
181     }
182 
183     /** Appends column name of a column in a root entity. */
184     protected void processColumn(StringBuffer buf, Expression nameExp) {
185         if (queryAssembler.supportsTableAliases()) {
186             String alias = queryAssembler.aliasForTable(getDbEntity());
187             buf.append(alias).append('.');
188         }
189 
190         buf.append(nameExp.getOperand(0));
191     }
192 
193     protected void processColumn(
194         StringBuffer buf,
195         DbAttribute dbAttr,
196         DbRelationship rel) {
197         String alias =
198             (queryAssembler.supportsTableAliases())
199                 ? queryAssembler.aliasForTable(
200                     (DbEntity) dbAttr.getEntity(),
201                     rel)
202                 : null;
203 
204         buf.append(dbAttr.getAliasedName(alias));
205     }
206 
207     protected void processColumn(StringBuffer buf, DbAttribute dbAttr) {
208         String alias =
209             (queryAssembler.supportsTableAliases())
210                 ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity())
211                 : null;
212 
213         buf.append(dbAttr.getAliasedName(alias));
214     }
215 
216 
217     /**
218      * Appends SQL code to the query buffer to handle <code>val</code> as a
219      * parameter to the PreparedStatement being built. Adds <code>val</code>
220      * into QueryAssembler parameter list. 
221      * 
222      * <p>If <code>val</code> is null, "NULL" is appended to the query. </p>
223      * 
224      * <p>If <code>val</code> is a DataObject, its  primary key value is 
225      * used as a parameter. <i>Only objects with a single column primary key 
226      * can be used.</i>
227      * 
228      * @param buf query buffer.
229      * 
230      * @param val object that should be appended as a literal to the query.
231      * Must be of one of "standard JDBC" types, null or a DataObject.
232      * 
233      * @param attr DbAttribute that has information on what type of parameter
234      * is being appended.
235      * 
236      */
237     protected void appendLiteral(
238         StringBuffer buf,
239         Object val,
240         DbAttribute attr,
241         Expression parentExpression) {
242         if (val == null) {
243             buf.append("NULL");
244         } else if (val instanceof DataObject) {
245             ObjectId id = ((DataObject) val).getObjectId();
246 
247             // check if this id is acceptable to be a parameter
248             if (id == null) {
249                 throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter.");
250             }
251 
252             if (id.isTemporary()) {
253                 throw new CayenneRuntimeException("Can't use NEW object as a query parameter.");
254             }
255 
256             Map snap = id.getIdSnapshot();
257             if (snap.size() != 1) {
258                 StringBuffer msg = new StringBuffer();
259                 msg
260                     .append("Object must have a single primary key column ")
261                     .append("to serve as a query parameter. ")
262                     .append("This object has ")
263                     .append(snap.size())
264                     .append(": ")
265                     .append(snap);
266 
267                 throw new CayenneRuntimeException(msg.toString());
268             }
269 
270             // checks have been passed, use id value
271             appendLiteralDirect(
272                 buf,
273                 snap.get(snap.keySet().iterator().next()),
274                 attr,
275                 parentExpression);
276         } else {
277             appendLiteralDirect(buf, val, attr, parentExpression);
278         }
279     }
280 
281     /**
282      * Appends SQL code to the query buffer to handle <code>val</code> as a
283      * parameter to the PreparedStatement being built. Adds <code>val</code>
284      * into QueryAssembler parameter list. 
285      * 
286      * 
287      * @param buf query buffer
288      * @param val object that should be appended as a literal to the query. 
289      * Must be of one of "standard JDBC" types. Can not be null.
290      */
291     protected void appendLiteralDirect(
292         StringBuffer buf,
293         Object val,
294         DbAttribute attr,
295         Expression parentExpression) {
296         buf.append('?');
297 
298         // we are hoping that when processing parameter list, 
299         // the correct type will be
300         // guessed without looking at DbAttribute...
301         queryAssembler.addToParamList(attr, val);
302     }
303 
304     /** 
305      * Returns database type of expression parameters or
306      * null if it can not be determined.
307      */
308     protected DbAttribute paramsDbType(Expression e) {
309         int len = e.getOperandCount();
310         // ignore unary expressions
311         if (len < 2) {
312             return null;
313         }
314 
315         // naive algorithm:
316 
317         // if at least one of the sibling operands is a
318         // OBJ_PATH expression, use its attribute type as
319         // a final answer.
320 
321         for (int i = 0; i < len; i++) {
322             Object op = e.getOperand(i);
323             if (op instanceof Expression) {
324                 Expression ope = (Expression) op;
325                 if (ope.getType() == Expression.OBJ_PATH) {
326 
327                     Iterator it = getObjEntity().resolvePathComponents(ope);
328                     while (it.hasNext()) {
329                         Object pathComp = it.next();
330 
331                         if (pathComp instanceof ObjAttribute) {
332                             return ((ObjAttribute) pathComp).getDbAttribute();
333                         }
334                     }
335                 }
336             }
337         }
338 
339         return null;
340     }
341 
342     /** Processes case when an OBJ_PATH expression ends with relationship.
343       * If this is a "to many" relationship, a join is added and a column
344       * expression for the target entity primary key. If this is a "to one"
345       * relationship, column expresion for the source foreign key is added.
346       */
347     protected void processRelTermination(
348         StringBuffer buf,
349         ObjRelationship rel) {
350 
351         Iterator dbRels = rel.getDbRelationships().iterator();
352 
353         // scan DbRelationships
354         while (dbRels.hasNext()) {
355             DbRelationship dbRel = (DbRelationship) dbRels.next();
356 
357             // if this is a last relationship in the path,
358             // it needs special handling
359             if (!dbRels.hasNext()) {
360                 processRelTermination(buf, dbRel);
361             } else {
362                 // find and add joins ....
363                 queryAssembler.dbRelationshipAdded(dbRel);
364             }
365         }
366     }
367 
368     /** 
369      * Handles case when a DB_NAME expression ends with relationship.
370      * If this is a "to many" relationship, a join is added and a column
371      * expression for the target entity primary key. If this is a "to one"
372      * relationship, column expresion for the source foreign key is added.
373      */
374     protected void processRelTermination(
375         StringBuffer buf,
376         DbRelationship rel) {
377 
378         if (rel.isToMany()) {
379             // append joins
380             queryAssembler.dbRelationshipAdded(rel);
381         }
382 
383         // get last DbRelationship on the list
384         List joins = rel.getJoins();
385         if (joins.size() != 1) {
386             StringBuffer msg = new StringBuffer();
387             msg
388                 .append("OBJ_PATH expressions are only supported ")
389                 .append("for a single-join relationships. ")
390                 .append("This relationship has ")
391                 .append(joins.size())
392                 .append(" joins.");
393 
394             throw new CayenneRuntimeException(msg.toString());
395         }
396 
397         DbAttributePair join = (DbAttributePair) joins.get(0);
398 
399         DbAttribute att = null;
400 
401         if (rel.isToMany()) {
402             DbEntity ent = (DbEntity) join.getTarget().getEntity();
403             List pk = ent.getPrimaryKey();
404             if (pk.size() != 1) {
405                 StringBuffer msg = new StringBuffer();
406                 msg
407                     .append("DB_NAME expressions can only support ")
408                     .append("targets with a single column PK. ")
409                     .append("This entity has ")
410                     .append(pk.size())
411                     .append(" columns in primary key.");
412 
413                 throw new CayenneRuntimeException(msg.toString());
414             }
415 
416             att = (DbAttribute) pk.get(0);
417         } else {
418             att = join.getSource();
419         }
420 
421         processColumn(buf, att);
422     }
423 }