KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > access > trans > QueryAssemblerHelper


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.access.trans;
57
58 import java.util.Iterator JavaDoc;
59 import java.util.List JavaDoc;
60 import java.util.Map JavaDoc;
61
62 import org.objectstyle.cayenne.CayenneRuntimeException;
63 import org.objectstyle.cayenne.DataObject;
64 import org.objectstyle.cayenne.ObjectId;
65 import org.objectstyle.cayenne.exp.Expression;
66 import org.objectstyle.cayenne.map.DbAttribute;
67 import org.objectstyle.cayenne.map.DbEntity;
68 import org.objectstyle.cayenne.map.DbJoin;
69 import org.objectstyle.cayenne.map.DbRelationship;
70 import org.objectstyle.cayenne.map.ObjAttribute;
71 import org.objectstyle.cayenne.map.ObjEntity;
72 import org.objectstyle.cayenne.map.ObjRelationship;
73
74 /**
75  * Translates parts of the query to SQL.
76  * Always works in the context of parent Translator.
77  *
78  * @author Andrei Adamchik
79  */

80 public abstract class QueryAssemblerHelper {
81
82     protected QueryAssembler queryAssembler;
83
84     public QueryAssemblerHelper() {
85     }
86
87     /** Creates QueryAssemblerHelper. Sets queryAssembler property. */
88     public QueryAssemblerHelper(QueryAssembler queryAssembler) {
89         this.queryAssembler = queryAssembler;
90     }
91
92     /** Returns parent QueryAssembler that uses this helper. */
93     public QueryAssembler getQueryAssembler() {
94         return queryAssembler;
95     }
96
97     public void setQueryAssembler(QueryAssembler queryAssembler) {
98         this.queryAssembler = queryAssembler;
99     }
100
101     /** Translates the part of parent translator's query that is supported
102      * by this PartTranslator. For example, QualifierTranslator will process
103      * qualifier expression, OrderingTranslator - ordering of the query.
104      * In the process of translation parent translator is notified of any
105      * join tables added (so that it can update its "FROM" clause).
106      * Also parent translator is consulted about table aliases to use
107      * when translating columns. */

108     public abstract String JavaDoc doTranslation();
109
110     public ObjEntity getObjEntity() {
111         return getQueryAssembler().getRootEntity();
112     }
113
114     public DbEntity getDbEntity() {
115         return getQueryAssembler().getRootDbEntity();
116     }
117
118     /** Processes parts of the OBJ_PATH expression. */
119     protected void appendObjPath(StringBuffer JavaDoc buf, Expression pathExp) {
120
121         Iterator JavaDoc it = getObjEntity().resolvePathComponents(pathExp);
122         ObjRelationship lastRelationship = null;
123
124         while (it.hasNext()) {
125             Object JavaDoc pathComp = it.next();
126
127             if (pathComp instanceof ObjRelationship) {
128                 ObjRelationship rel = (ObjRelationship) pathComp;
129
130                 // if this is a last relationship in the path,
131
// it needs special handling
132
if (!it.hasNext()) {
133                     processRelTermination(buf, rel);
134                 }
135                 else {
136                     // find and add joins ....
137
Iterator JavaDoc relit = rel.getDbRelationships().iterator();
138                     while (relit.hasNext()) {
139                         queryAssembler.dbRelationshipAdded((DbRelationship) relit.next());
140                     }
141                 }
142                 lastRelationship = rel;
143             }
144             else {
145                 ObjAttribute objAttr = (ObjAttribute) pathComp;
146                 if (lastRelationship != null) {
147                     List JavaDoc lastDbRelList = lastRelationship.getDbRelationships();
148                     DbRelationship lastDbRel =
149                         (DbRelationship) lastDbRelList.get(lastDbRelList.size() - 1);
150                     processColumn(buf, objAttr.getDbAttribute(), lastDbRel);
151                 }
152                 else {
153                     processColumn(buf, objAttr.getDbAttribute());
154                 }
155             }
156         }
157     }
158
159     protected void appendDbPath(StringBuffer JavaDoc buf, Expression pathExp) {
160         Iterator JavaDoc it = getDbEntity().resolvePathComponents(pathExp);
161
162         while (it.hasNext()) {
163             Object JavaDoc pathComp = it.next();
164             if (pathComp instanceof DbRelationship) {
165                 DbRelationship rel = (DbRelationship) pathComp;
166
167                 // if this is a last relationship in the path,
168
// it needs special handling
169
if (!it.hasNext()) {
170                     processRelTermination(buf, rel);
171                 }
172                 else {
173                     // find and add joins ....
174
queryAssembler.dbRelationshipAdded(rel);
175                 }
176             }
177             else {
178                 DbAttribute dbAttr = (DbAttribute) pathComp;
179                 processColumn(buf, dbAttr);
180             }
181         }
182     }
183
184     /** Appends column name of a column in a root entity. */
185     protected void processColumn(StringBuffer JavaDoc buf, Expression nameExp) {
186         if (queryAssembler.supportsTableAliases()) {
187             String JavaDoc alias = queryAssembler.aliasForTable(getDbEntity());
188             buf.append(alias).append('.');
189         }
190
191         buf.append(nameExp.getOperand(0));
192     }
193
194     protected void processColumn(
195         StringBuffer JavaDoc buf,
196         DbAttribute dbAttr,
197         DbRelationship relationship) {
198         String JavaDoc alias = null;
199
200         if (queryAssembler.supportsTableAliases()) {
201
202             if (relationship != null) {
203                 alias = queryAssembler.aliasForTable(
204                         (DbEntity) dbAttr.getEntity(),
205                         relationship);
206             }
207
208             // sometimes lookup for relationship fails (any specific case other than
209
// relationship being null?), so lookup by entity. Note that as CAY-194
210
// shows, lookup by DbEntity may produce incorrect results for
211
// reflexive relationships.
212
if (alias == null) {
213                 alias = queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity());
214             }
215         }
216
217         buf.append(dbAttr.getAliasedName(alias));
218     }
219
220     protected void processColumn(StringBuffer JavaDoc buf, DbAttribute dbAttr) {
221         String JavaDoc alias =
222             (queryAssembler.supportsTableAliases())
223                 ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity())
224                 : null;
225
226         buf.append(dbAttr.getAliasedName(alias));
227     }
228
229     /**
230      * Appends SQL code to the query buffer to handle <code>val</code> as a
231      * parameter to the PreparedStatement being built. Adds <code>val</code>
232      * into QueryAssembler parameter list.
233      *
234      * <p>If <code>val</code> is null, "NULL" is appended to the query. </p>
235      *
236      * <p>If <code>val</code> is a DataObject, its primary key value is
237      * used as a parameter. <i>Only objects with a single column primary key
238      * can be used.</i>
239      *
240      * @param buf query buffer.
241      *
242      * @param val object that should be appended as a literal to the query.
243      * Must be of one of "standard JDBC" types, null or a DataObject.
244      *
245      * @param attr DbAttribute that has information on what type of parameter
246      * is being appended.
247      *
248      */

249     protected void appendLiteral(
250         StringBuffer JavaDoc buf,
251         Object JavaDoc val,
252         DbAttribute attr,
253         Expression parentExpression) {
254         if (val == null) {
255             buf.append("NULL");
256         }
257         else if (val instanceof DataObject) {
258             ObjectId id = ((DataObject) val).getObjectId();
259
260             // check if this id is acceptable to be a parameter
261
if (id == null) {
262                 throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter.");
263             }
264
265             if (id.isTemporary()) {
266                 throw new CayenneRuntimeException("Can't use NEW object as a query parameter.");
267             }
268
269             Map JavaDoc snap = id.getIdSnapshot();
270             if (snap.size() != 1) {
271                 StringBuffer JavaDoc msg = new StringBuffer JavaDoc();
272                 msg
273                     .append("Object must have a single primary key column ")
274                     .append("to serve as a query parameter. ")
275                     .append("This object has ")
276                     .append(snap.size())
277                     .append(": ")
278                     .append(snap);
279
280                 throw new CayenneRuntimeException(msg.toString());
281             }
282
283             // checks have been passed, use id value
284
appendLiteralDirect(
285                 buf,
286                 snap.get(snap.keySet().iterator().next()),
287                 attr,
288                 parentExpression);
289         }
290         else {
291             appendLiteralDirect(buf, val, attr, parentExpression);
292         }
293     }
294
295     /**
296      * Appends SQL code to the query buffer to handle <code>val</code> as a
297      * parameter to the PreparedStatement being built. Adds <code>val</code>
298      * into QueryAssembler parameter list.
299      *
300      *
301      * @param buf query buffer
302      * @param val object that should be appended as a literal to the query.
303      * Must be of one of "standard JDBC" types. Can not be null.
304      */

305     protected void appendLiteralDirect(
306         StringBuffer JavaDoc buf,
307         Object JavaDoc val,
308         DbAttribute attr,
309         Expression parentExpression) {
310         buf.append('?');
311
312         // we are hoping that when processing parameter list,
313
// the correct type will be
314
// guessed without looking at DbAttribute...
315
queryAssembler.addToParamList(attr, val);
316     }
317
318     /**
319      * Returns database type of expression parameters or
320      * null if it can not be determined.
321      */

322     protected DbAttribute paramsDbType(Expression e) {
323         int len = e.getOperandCount();
324         // ignore unary expressions
325
if (len < 2) {
326             return null;
327         }
328
329         // naive algorithm:
330

331         // if at least one of the sibling operands is a
332
// OBJ_PATH or DB_PATH expression, use its attribute type as
333
// a final answer.
334

335         // find attribute or relationship matching the value
336
DbAttribute attribute = null;
337         DbRelationship relationship = null;
338         for (int i = 0; i < len; i++) {
339             Object JavaDoc op = e.getOperand(i);
340
341             if (op instanceof Expression) {
342                 Expression expression = (Expression) op;
343                 if (expression.getType() == Expression.OBJ_PATH) {
344                     Object JavaDoc last = getObjEntity().lastPathComponent(expression);
345                     if (last instanceof ObjAttribute) {
346                         attribute = ((ObjAttribute) last).getDbAttribute();
347                         break;
348                     }
349                     else if (last instanceof ObjRelationship) {
350                         ObjRelationship objRelationship = (ObjRelationship) last;
351                         List JavaDoc dbPath = objRelationship.getDbRelationships();
352                         if (dbPath.size() > 0) {
353                             relationship = (DbRelationship) dbPath.get(dbPath.size() - 1);
354                             break;
355                         }
356                     }
357                 }
358                 else if (expression.getType() == Expression.DB_PATH) {
359                     Object JavaDoc last = getDbEntity().lastPathComponent(expression);
360                     if (last instanceof DbAttribute) {
361                         attribute = (DbAttribute) last;
362                         break;
363                     }
364                     else if (last instanceof DbRelationship) {
365                         relationship = (DbRelationship) last;
366                         break;
367                     }
368                 }
369             }
370         }
371
372         if (attribute != null) {
373             return attribute;
374         }
375
376         if (relationship != null) {
377             // Can't properly handle multiple joins....
378
if (relationship.getJoins().size() == 1) {
379                 DbJoin join = (DbJoin) relationship.getJoins().get(0);
380                 return join.getSource();
381             }
382         }
383
384         return null;
385     }
386
387     /** Processes case when an OBJ_PATH expression ends with relationship.
388       * If this is a "to many" relationship, a join is added and a column
389       * expression for the target entity primary key. If this is a "to one"
390       * relationship, column expresion for the source foreign key is added.
391       */

392     protected void processRelTermination(StringBuffer JavaDoc buf, ObjRelationship rel) {
393
394         Iterator JavaDoc dbRels = rel.getDbRelationships().iterator();
395
396         // scan DbRelationships
397
while (dbRels.hasNext()) {
398             DbRelationship dbRel = (DbRelationship) dbRels.next();
399
400             // if this is a last relationship in the path,
401
// it needs special handling
402
if (!dbRels.hasNext()) {
403                 processRelTermination(buf, dbRel);
404             }
405             else {
406                 // find and add joins ....
407
queryAssembler.dbRelationshipAdded(dbRel);
408             }
409         }
410     }
411
412     /**
413      * Handles case when a DB_NAME expression ends with relationship.
414      * If this is a "to many" relationship, a join is added and a column
415      * expression for the target entity primary key. If this is a "to one"
416      * relationship, column expresion for the source foreign key is added.
417      */

418     protected void processRelTermination(StringBuffer JavaDoc buf, DbRelationship rel) {
419
420         if (rel.isToMany()) {
421             // append joins
422
queryAssembler.dbRelationshipAdded(rel);
423         }
424
425         // get last DbRelationship on the list
426
List JavaDoc joins = rel.getJoins();
427         if (joins.size() != 1) {
428             StringBuffer JavaDoc msg = new StringBuffer JavaDoc();
429             msg
430                 .append("OBJ_PATH expressions are only supported ")
431                 .append("for a single-join relationships. ")
432                 .append("This relationship has ")
433                 .append(joins.size())
434                 .append(" joins.");
435
436             throw new CayenneRuntimeException(msg.toString());
437         }
438
439         DbJoin join = (DbJoin) joins.get(0);
440
441         DbAttribute att = null;
442
443         if (rel.isToMany()) {
444             DbEntity ent = (DbEntity) join.getRelationship().getTargetEntity();
445             List JavaDoc pk = ent.getPrimaryKey();
446             if (pk.size() != 1) {
447                 StringBuffer JavaDoc msg = new StringBuffer JavaDoc();
448                 msg
449                     .append("DB_NAME expressions can only support ")
450                     .append("targets with a single column PK. ")
451                     .append("This entity has ")
452                     .append(pk.size())
453                     .append(" columns in primary key.");
454
455                 throw new CayenneRuntimeException(msg.toString());
456             }
457
458             att = (DbAttribute) pk.get(0);
459         }
460         else {
461             att = join.getSource();
462         }
463
464         processColumn(buf, att);
465     }
466 }
467
Popular Tags