KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20 package org.apache.cayenne.access.trans;
21
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import org.apache.cayenne.CayenneRuntimeException;
27 import org.apache.cayenne.ObjectId;
28 import org.apache.cayenne.Persistent;
29 import org.apache.cayenne.exp.Expression;
30 import org.apache.cayenne.map.DbAttribute;
31 import org.apache.cayenne.map.DbEntity;
32 import org.apache.cayenne.map.DbJoin;
33 import org.apache.cayenne.map.DbRelationship;
34 import org.apache.cayenne.map.ObjAttribute;
35 import org.apache.cayenne.map.ObjEntity;
36 import org.apache.cayenne.map.ObjRelationship;
37
38 /**
39  * Translates parts of the query to SQL.
40  * Always works in the context of parent Translator.
41  *
42  * @author Andrus Adamchik
43  */

44 public abstract class QueryAssemblerHelper {
45
46     protected QueryAssembler queryAssembler;
47
48     public QueryAssemblerHelper() {
49     }
50
51     /** Creates QueryAssemblerHelper. Sets queryAssembler property. */
52     public QueryAssemblerHelper(QueryAssembler queryAssembler) {
53         this.queryAssembler = queryAssembler;
54     }
55
56     /** Returns parent QueryAssembler that uses this helper. */
57     public QueryAssembler getQueryAssembler() {
58         return queryAssembler;
59     }
60
61     public void setQueryAssembler(QueryAssembler queryAssembler) {
62         this.queryAssembler = queryAssembler;
63     }
64
65     /** Translates the part of parent translator's query that is supported
66      * by this PartTranslator. For example, QualifierTranslator will process
67      * qualifier expression, OrderingTranslator - ordering of the query.
68      * In the process of translation parent translator is notified of any
69      * join tables added (so that it can update its "FROM" clause).
70      * Also parent translator is consulted about table aliases to use
71      * when translating columns. */

72     public abstract String JavaDoc doTranslation();
73
74     public ObjEntity getObjEntity() {
75         return getQueryAssembler().getRootEntity();
76     }
77
78     public DbEntity getDbEntity() {
79         return getQueryAssembler().getRootDbEntity();
80     }
81
82     /** Processes parts of the OBJ_PATH expression. */
83     protected void appendObjPath(StringBuffer JavaDoc buf, Expression pathExp) {
84
85         Iterator JavaDoc it = getObjEntity().resolvePathComponents(pathExp);
86         ObjRelationship lastRelationship = null;
87
88         while (it.hasNext()) {
89             Object JavaDoc pathComp = it.next();
90
91             if (pathComp instanceof ObjRelationship) {
92                 ObjRelationship rel = (ObjRelationship) pathComp;
93
94                 // if this is a last relationship in the path,
95
// it needs special handling
96
if (!it.hasNext()) {
97                     processRelTermination(buf, rel);
98                 }
99                 else {
100                     // find and add joins ....
101
Iterator JavaDoc relit = rel.getDbRelationships().iterator();
102                     while (relit.hasNext()) {
103                         queryAssembler.dbRelationshipAdded((DbRelationship) relit.next());
104                     }
105                 }
106                 lastRelationship = rel;
107             }
108             else {
109                 ObjAttribute objAttr = (ObjAttribute) pathComp;
110                 if (lastRelationship != null) {
111                     List JavaDoc lastDbRelList = lastRelationship.getDbRelationships();
112                     DbRelationship lastDbRel =
113                         (DbRelationship) lastDbRelList.get(lastDbRelList.size() - 1);
114                     processColumn(buf, objAttr.getDbAttribute(), lastDbRel);
115                 }
116                 else {
117                     processColumn(buf, objAttr.getDbAttribute());
118                 }
119             }
120         }
121     }
122
123     protected void appendDbPath(StringBuffer JavaDoc buf, Expression pathExp) {
124         Iterator JavaDoc it = getDbEntity().resolvePathComponents(pathExp);
125
126         while (it.hasNext()) {
127             Object JavaDoc pathComp = it.next();
128             if (pathComp instanceof DbRelationship) {
129                 DbRelationship rel = (DbRelationship) pathComp;
130
131                 // if this is a last relationship in the path,
132
// it needs special handling
133
if (!it.hasNext()) {
134                     processRelTermination(buf, rel);
135                 }
136                 else {
137                     // find and add joins ....
138
queryAssembler.dbRelationshipAdded(rel);
139                 }
140             }
141             else {
142                 DbAttribute dbAttr = (DbAttribute) pathComp;
143                 processColumn(buf, dbAttr);
144             }
145         }
146     }
147
148     /** Appends column name of a column in a root entity. */
149     protected void processColumn(StringBuffer JavaDoc buf, Expression nameExp) {
150         if (queryAssembler.supportsTableAliases()) {
151             String JavaDoc alias = queryAssembler.aliasForTable(getDbEntity());
152             buf.append(alias).append('.');
153         }
154
155         buf.append(nameExp.getOperand(0));
156     }
157
158     protected void processColumn(
159         StringBuffer JavaDoc buf,
160         DbAttribute dbAttr,
161         DbRelationship relationship) {
162         String JavaDoc alias = null;
163
164         if (queryAssembler.supportsTableAliases()) {
165
166             if (relationship != null) {
167                 alias = queryAssembler.aliasForTable(
168                         (DbEntity) dbAttr.getEntity(),
169                         relationship);
170             }
171
172             // sometimes lookup for relationship fails (any specific case other than
173
// relationship being null?), so lookup by entity. Note that as CAY-194
174
// shows, lookup by DbEntity may produce incorrect results for
175
// reflexive relationships.
176
if (alias == null) {
177                 alias = queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity());
178             }
179         }
180
181         buf.append(dbAttr.getAliasedName(alias));
182     }
183
184     protected void processColumn(StringBuffer JavaDoc buf, DbAttribute dbAttr) {
185         String JavaDoc alias =
186             (queryAssembler.supportsTableAliases())
187                 ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity())
188                 : null;
189
190         buf.append(dbAttr.getAliasedName(alias));
191     }
192
193     /**
194      * Appends SQL code to the query buffer to handle <code>val</code> as a
195      * parameter to the PreparedStatement being built. Adds <code>val</code>
196      * into QueryAssembler parameter list.
197      *
198      * <p>If <code>val</code> is null, "NULL" is appended to the query. </p>
199      *
200      * <p>If <code>val</code> is a DataObject, its primary key value is
201      * used as a parameter. <i>Only objects with a single column primary key
202      * can be used.</i>
203      *
204      * @param buf query buffer.
205      *
206      * @param val object that should be appended as a literal to the query.
207      * Must be of one of "standard JDBC" types, null or a DataObject.
208      *
209      * @param attr DbAttribute that has information on what type of parameter
210      * is being appended.
211      *
212      */

213     protected void appendLiteral(
214         StringBuffer JavaDoc buf,
215         Object JavaDoc val,
216         DbAttribute attr,
217         Expression parentExpression) {
218         if (val == null) {
219             buf.append("NULL");
220         }
221         else if (val instanceof Persistent) {
222             ObjectId id = ((Persistent) val).getObjectId();
223
224             // check if this id is acceptable to be a parameter
225
if (id == null) {
226                 throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter.");
227             }
228
229             if (id.isTemporary()) {
230                 throw new CayenneRuntimeException("Can't use NEW object as a query parameter.");
231             }
232
233             Map JavaDoc snap = id.getIdSnapshot();
234             if (snap.size() != 1) {
235                 StringBuffer JavaDoc msg = new StringBuffer JavaDoc();
236                 msg
237                     .append("Object must have a single primary key column ")
238                     .append("to serve as a query parameter. ")
239                     .append("This object has ")
240                     .append(snap.size())
241                     .append(": ")
242                     .append(snap);
243
244                 throw new CayenneRuntimeException(msg.toString());
245             }
246
247             // checks have been passed, use id value
248
appendLiteralDirect(
249                 buf,
250                 snap.get(snap.keySet().iterator().next()),
251                 attr,
252                 parentExpression);
253         }
254         else {
255             appendLiteralDirect(buf, val, attr, parentExpression);
256         }
257     }
258
259     /**
260      * Appends SQL code to the query buffer to handle <code>val</code> as a
261      * parameter to the PreparedStatement being built. Adds <code>val</code>
262      * into QueryAssembler parameter list.
263      *
264      *
265      * @param buf query buffer
266      * @param val object that should be appended as a literal to the query.
267      * Must be of one of "standard JDBC" types. Can not be null.
268      */

269     protected void appendLiteralDirect(
270         StringBuffer JavaDoc buf,
271         Object JavaDoc val,
272         DbAttribute attr,
273         Expression parentExpression) {
274         buf.append('?');
275
276         // we are hoping that when processing parameter list,
277
// the correct type will be
278
// guessed without looking at DbAttribute...
279
queryAssembler.addToParamList(attr, val);
280     }
281
282     /**
283      * Returns database type of expression parameters or
284      * null if it can not be determined.
285      */

286     protected DbAttribute paramsDbType(Expression e) {
287         int len = e.getOperandCount();
288         // ignore unary expressions
289
if (len < 2) {
290             return null;
291         }
292
293         // naive algorithm:
294

295         // if at least one of the sibling operands is a
296
// OBJ_PATH or DB_PATH expression, use its attribute type as
297
// a final answer.
298

299         // find attribute or relationship matching the value
300
DbAttribute attribute = null;
301         DbRelationship relationship = null;
302         for (int i = 0; i < len; i++) {
303             Object JavaDoc op = e.getOperand(i);
304
305             if (op instanceof Expression) {
306                 Expression expression = (Expression) op;
307                 if (expression.getType() == Expression.OBJ_PATH) {
308                     Object JavaDoc last = getObjEntity().lastPathComponent(expression);
309                     if (last instanceof ObjAttribute) {
310                         attribute = ((ObjAttribute) last).getDbAttribute();
311                         break;
312                     }
313                     else if (last instanceof ObjRelationship) {
314                         ObjRelationship objRelationship = (ObjRelationship) last;
315                         List JavaDoc dbPath = objRelationship.getDbRelationships();
316                         if (dbPath.size() > 0) {
317                             relationship = (DbRelationship) dbPath.get(dbPath.size() - 1);
318                             break;
319                         }
320                     }
321                 }
322                 else if (expression.getType() == Expression.DB_PATH) {
323                     Object JavaDoc last = getDbEntity().lastPathComponent(expression);
324                     if (last instanceof DbAttribute) {
325                         attribute = (DbAttribute) last;
326                         break;
327                     }
328                     else if (last instanceof DbRelationship) {
329                         relationship = (DbRelationship) last;
330                         break;
331                     }
332                 }
333             }
334         }
335
336         if (attribute != null) {
337             return attribute;
338         }
339
340         if (relationship != null) {
341             // Can't properly handle multiple joins....
342
if (relationship.getJoins().size() == 1) {
343                 DbJoin join = (DbJoin) relationship.getJoins().get(0);
344                 return join.getSource();
345             }
346         }
347
348         return null;
349     }
350
351     /** Processes case when an OBJ_PATH expression ends with relationship.
352       * If this is a "to many" relationship, a join is added and a column
353       * expression for the target entity primary key. If this is a "to one"
354       * relationship, column expresion for the source foreign key is added.
355       */

356     protected void processRelTermination(StringBuffer JavaDoc buf, ObjRelationship rel) {
357
358         Iterator JavaDoc dbRels = rel.getDbRelationships().iterator();
359
360         // scan DbRelationships
361
while (dbRels.hasNext()) {
362             DbRelationship dbRel = (DbRelationship) dbRels.next();
363
364             // if this is a last relationship in the path,
365
// it needs special handling
366
if (!dbRels.hasNext()) {
367                 processRelTermination(buf, dbRel);
368             }
369             else {
370                 // find and add joins ....
371
queryAssembler.dbRelationshipAdded(dbRel);
372             }
373         }
374     }
375
376     /**
377      * Handles case when a DB_NAME expression ends with relationship.
378      * If this is a "to many" relationship, a join is added and a column
379      * expression for the target entity primary key. If this is a "to one"
380      * relationship, column expresion for the source foreign key is added.
381      */

382     protected void processRelTermination(StringBuffer JavaDoc buf, DbRelationship rel) {
383
384         if (rel.isToMany()) {
385             // append joins
386
queryAssembler.dbRelationshipAdded(rel);
387         }
388
389         // get last DbRelationship on the list
390
List JavaDoc joins = rel.getJoins();
391         if (joins.size() != 1) {
392             StringBuffer JavaDoc msg = new StringBuffer JavaDoc();
393             msg
394                 .append("OBJ_PATH expressions are only supported ")
395                 .append("for a single-join relationships. ")
396                 .append("This relationship has ")
397                 .append(joins.size())
398                 .append(" joins.");
399
400             throw new CayenneRuntimeException(msg.toString());
401         }
402
403         DbJoin join = (DbJoin) joins.get(0);
404
405         DbAttribute att = null;
406
407         if (rel.isToMany()) {
408             DbEntity ent = (DbEntity) join.getRelationship().getTargetEntity();
409             List JavaDoc pk = ent.getPrimaryKey();
410             if (pk.size() != 1) {
411                 StringBuffer JavaDoc msg = new StringBuffer JavaDoc();
412                 msg
413                     .append("DB_NAME expressions can only support ")
414                     .append("targets with a single column PK. ")
415                     .append("This entity has ")
416                     .append(pk.size())
417                     .append(" columns in primary key.");
418
419                 throw new CayenneRuntimeException(msg.toString());
420             }
421
422             att = (DbAttribute) pk.get(0);
423         }
424         else {
425             att = join.getSource();
426         }
427
428         processColumn(buf, att);
429     }
430 }
431
Popular Tags