001: /**********************************************************************
002: Copyright (c) 2003 Erik Bengtson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015:
016: Contributors:
017: 2003 Andy Jefferson - coding standards
018: 2005 Andy Jefferson - added handling for joins to related fields
019: 2005 Andy Jefferson - added fix for use of "this" to avoid LEFT OUTER JOIN
020: ...
021: **********************************************************************/package org.jpox.store.expression;
022:
023: import java.util.Iterator;
024:
025: import org.jpox.ClassLoaderResolver;
026: import org.jpox.api.ApiAdapter;
027: import org.jpox.exceptions.JPOXUserException;
028: import org.jpox.jdo.exceptions.ClassNotPersistenceCapableException;
029: import org.jpox.metadata.AbstractClassMetaData;
030: import org.jpox.metadata.AbstractMemberMetaData;
031: import org.jpox.metadata.DiscriminatorMetaData;
032: import org.jpox.metadata.DiscriminatorStrategy;
033: import org.jpox.metadata.InheritanceStrategy;
034: import org.jpox.store.DatastoreClass;
035: import org.jpox.store.DatastoreContainerObject;
036: import org.jpox.store.DatastoreIdentifier;
037: import org.jpox.store.IdentifierFactory;
038: import org.jpox.store.mapping.BigDecimalMapping;
039: import org.jpox.store.mapping.BigIntegerMapping;
040: import org.jpox.store.mapping.BooleanMapping;
041: import org.jpox.store.mapping.ByteMapping;
042: import org.jpox.store.mapping.CharacterMapping;
043: import org.jpox.store.mapping.DiscriminatorMapping;
044: import org.jpox.store.mapping.DoubleMapping;
045: import org.jpox.store.mapping.EmbeddedMapping;
046: import org.jpox.store.mapping.EmbeddedPCMapping;
047: import org.jpox.store.mapping.FloatMapping;
048: import org.jpox.store.mapping.ObjectIdClassMapping;
049: import org.jpox.store.mapping.IntegerMapping;
050: import org.jpox.store.mapping.JavaTypeMapping;
051: import org.jpox.store.mapping.LongMapping;
052: import org.jpox.store.mapping.PersistenceCapableMapping;
053: import org.jpox.store.mapping.ReferenceMapping;
054: import org.jpox.store.mapping.ShortMapping;
055: import org.jpox.store.mapping.SqlDateMapping;
056: import org.jpox.store.mapping.SqlTimeMapping;
057: import org.jpox.store.mapping.SqlTimestampMapping;
058: import org.jpox.store.mapping.StringMapping;
059: import org.jpox.util.JPOXLogger;
060:
061: /**
062: * Representation of an Object expression in a Query.
063: * Let's take an example :-
064: * <PRE>
065: * We have classes A and B, and A contains a reference to B "b".
066: * If we do a JDOQL query for class A of "b.someField == value" then
067: * "b" is interpreted first and an ObjectExpression is created to represent
068: * that object (of type B).
069: * </PRE>
070: * The expression has an associated TableExpression, and a list of
071: * expressions which represent the datastore columns identifying the
072: * object (the PK fields/columns).
073: *
074: * @version $Revision: 1.59 $
075: **/
076: public class ObjectExpression extends ScalarExpression {
077: protected ScalarExpression conditionExpr;
078: private Class castType;
079:
080: /** Name of the field that this object represents. Null typically means that this is the candidate object. */
081: private String fieldName;
082:
083: /** Type of the field that this object represents. */
084: private String fieldType;
085:
086: /** Flag for whether we are using a related table and so dont need to join again in field access. */
087: // TODO This is always true!!!! Why ?????????
088: private boolean usingRelatedTable = true;
089:
090: protected ObjectExpression(QueryExpression qs) {
091: super (qs);
092: }
093:
094: /**
095: * Constructor for an object expression, using the mapping of the field, and the expression for the table.
096: * @param qs The Query Statement
097: * @param mapping The mapping for the field whose object we are expressing
098: * @param te The expression for the table of the object.
099: */
100: public ObjectExpression(QueryExpression qs,
101: JavaTypeMapping mapping, LogicSetExpression te) {
102: super (qs, mapping, te);
103: }
104:
105: /**
106: * Constructor for an object expression, using the mapping of the field (which has no datastore columns),
107: * the expression for its table, the mapping for a field in another table to join to, and the expression
108: * for the other table. This is for use in 2 situations :-
109: * <UL>
110: * <LI>a 1-1 bidirectional single-FK relation where one side has its FK stored in the other object,
111: * so we need to left outer join to get that field.</LI>
112: * <LI>a 1-N bidirectional join table relation where the element side has to navigate via the
113: * join table to get to the owner object.</LI>
114: * </UL>
115: * @param qs The Query Statement
116: * @param mapping The mapping for the field whose object we are expressing
117: * @param te The expression for the table of the object
118: * @param refMapping The mapping of the field in another table that we join to
119: * @param teTarget The expression for the other table that we are joining to.
120: * @param selectMapping The mapping that we should select in the other table
121: */
122: public ObjectExpression(QueryExpression qs,
123: JavaTypeMapping mapping, LogicSetExpression te,
124: JavaTypeMapping refMapping, LogicSetExpression teTarget,
125: JavaTypeMapping selectMapping) {
126: this (qs);
127:
128: // Join from the ID field of this table to the related field of the related table
129: ScalarExpression sourceExpr = mapping.getDatastoreContainer()
130: .getIDMapping().newScalarExpression(qs, te);
131: ScalarExpression targetExpr = refMapping.newScalarExpression(
132: qs, teTarget);
133: qs.leftOuterJoin(sourceExpr, targetExpr, teTarget, true, true);
134:
135: this .mapping = selectMapping;
136: this .te = teTarget;
137: for (int i = 0; i < this .mapping.getNumberOfDatastoreFields(); i++) {
138: expressionList.addExpression(new DatastoreFieldExpression(
139: qs, this .mapping.getDataStoreMapping(i)
140: .getDatastoreField(), teTarget));
141: }
142: st.append(expressionList.toString());
143: usingRelatedTable = true;
144: }
145:
146: /**
147: * Construct an object expression conditioned to a boolean expression
148: * If this expression is an operand of an operation with result type Boolean has the following semantic
149: *
150: * if (conditionExpr == null )
151: * return (otherExpression op expr);
152: * else
153: * return (otherExpression op expr) & conditionExpr;
154: *
155: * @param qs the QueryExpression
156: * @param expr the expression
157: * @param conditionExpr the conditional boolean expression
158: * @param te the TableExpression
159: */
160: public ObjectExpression(QueryExpression qs, ScalarExpression expr,
161: ScalarExpression conditionExpr, LogicSetExpression te) {
162: super (qs);
163:
164: this .te = te;
165: this .mapping = expr.mapping;
166: this .st.append(expr.st.toString());
167: this .expressionList = expr.expressionList;
168: this .conditionExpr = conditionExpr;
169: }
170:
171: /**
172: * Method to change the expression to use only the first datastore field.
173: * This is used where we want to use the expression in an aggregate and
174: * only can use one datastore field. Package permission to prevent external access.
175: */
176: void useFirstDatastoreFieldOnly() {
177: if (mapping.getNumberOfDatastoreFields() <= 1) {
178: // Do nothing
179: return;
180: }
181:
182: // Replace the expressionList and SQL as if starting from scratch
183: expressionList = new ExpressionList();
184: expressionList
185: .addExpression(new DatastoreFieldExpression(qs, mapping
186: .getDataStoreMapping(0).getDatastoreField(), te));
187: st.clearStatement();
188: st.append(expressionList.toString());
189: }
190:
191: /**
192: * Convenience method for the case where the mapping being used is a PersistenceCapableMapping
193: * and where we want to represent the identity instead of the object represented by that
194: * mapping. This changes the mapping to use the id.
195: */
196: public void useIdentityFormOfPCMapping() {
197: if (mapping instanceof PersistenceCapableMapping) {
198: mapping = new ObjectIdClassMapping(
199: (PersistenceCapableMapping) mapping);
200: }
201: }
202:
203: /**
204: * Convenience method to add an outer join suffix.
205: * Particularly for use with Oracle (8) which uses syntax like table1.col1 = table2.col2 (+),
206: * @param suffix The suffix
207: */
208: public void addOuterJoinSuffix(String suffix) {
209: if (suffix != null) {
210: st.append(suffix);
211: }
212: }
213:
214: /**
215: * Cast operator. Called when the query contains "(type)obj" where "obj" is this object.
216: * @param castType The type we cast this object to
217: * @return Scalar expression representing the cast object.
218: */
219: public ScalarExpression cast(Class castType) {
220: ObjectExpression objectCast;
221: LogicSetExpression te = qs.getTableExpression(this .qs
222: .getStoreManager()
223: .getDatastoreClass(castType.getName(),
224: qs.getClassLoaderResolver()).getIdentifier());
225: DatastoreClass dc = this .qs.getStoreManager()
226: .getDatastoreClass(castType.getName(),
227: qs.getClassLoaderResolver());
228: if (te == null) {
229: IdentifierFactory idFactory = qs.getStoreManager()
230: .getIdentifierFactory();
231: String jtIdentifier = this .te.getAlias().getIdentifier();
232: if (castType != null
233: && !castType.getName().equals(mapping.getType())) {
234: String castTypeName = castType.getName();
235: jtIdentifier = idFactory.newIdentifier(
236: this .te.getAlias(),
237: castTypeName.substring(castTypeName
238: .lastIndexOf('.') + 1)).getIdentifier();
239: }
240:
241: DatastoreIdentifier jtRangeVar = idFactory.newIdentifier(
242: IdentifierFactory.TABLE, jtIdentifier);
243: LogicSetExpression jtTblExpr = qs
244: .getTableExpression(jtRangeVar);
245:
246: if (jtTblExpr == null) {
247: jtTblExpr = qs.newTableExpression(dc, jtRangeVar);
248: }
249: te = jtTblExpr;
250: qs.leftOuterJoin(this , dc.getIDMapping()
251: .newScalarExpression(qs, jtTblExpr), jtTblExpr,
252: true, true);
253: }
254: objectCast = new ObjectExpression(qs, dc.getIDMapping(), te);
255: objectCast.conditionExpr = this .conditionExpr;
256: return objectCast;
257: }
258:
259: /**
260: * Equals operator. Called when the query contains "obj == value" where "obj" is this object.
261: * @param expr The expression we compare with (the right-hand-side in the query)
262: * @return Boolean expression representing the comparison.
263: */
264: public BooleanExpression eq(ScalarExpression expr) {
265: BooleanExpression bExpr = null;
266: if (expr instanceof NullLiteral) {
267: for (int i = 0; i < this .expressionList.size(); i++) {
268: if (bExpr == null) {
269: bExpr = expr.eq(this .expressionList
270: .getExpression(i));
271: } else {
272: bExpr = bExpr.and(expr.eq(this .expressionList
273: .getExpression(i)));
274: }
275: }
276: } else if (literalIsValidForSimpleComparison(expr)) {
277: if (this .expressionList.size() > 1) {
278: // More than 1 value to compare with a literal!
279: bExpr = super .eq(expr);
280: } else {
281: // Just do a direct comparison with the basic literals
282: bExpr = new BooleanExpression(this , OP_EQ, expr);
283: }
284: } else if (expr instanceof ObjectLiteral) {
285: bExpr = expr.eq(this );
286: } else if (expr instanceof ObjectExpression) {
287: for (int i = 0; i < this .expressionList.size(); i++) {
288: ScalarExpression source = this .expressionList
289: .getExpression(i);
290: ScalarExpression target = expr.expressionList
291: .getExpression(i);
292: if (bExpr == null) {
293: bExpr = source.eq(target);
294: } else {
295: bExpr = bExpr.and(source.eq(target));
296: }
297: }
298: } else if (expr instanceof UnboundVariable) {
299: if (((UnboundVariable) expr).getVariableType() == null) {
300: // Set the variable type to this objects type
301: ((UnboundVariable) expr).setVariableType(qs
302: .getClassLoaderResolver().classForName(
303: fieldType));
304: }
305: bExpr = expr.eq(this );
306: } else if (expr instanceof BooleanBitColumnExpression) {
307: bExpr = null;
308: } else {
309: bExpr = super .eq(expr);
310: }
311:
312: if (conditionExpr != null) {
313: return new BooleanExpression(conditionExpr, OP_AND, bExpr);
314: }
315:
316: return bExpr;
317: }
318:
319: /**
320: * Not equals operator. Called when the query contains "obj != value" where "obj" is this object.
321: * @param expr The expression we compare with (the right-hand-side in the query)
322: * @return Boolean expression representing the comparison.
323: */
324: public BooleanExpression noteq(ScalarExpression expr) {
325: BooleanExpression bExpr = null;
326: if (expr instanceof NullLiteral) {
327: for (int i = 0; i < this .expressionList.size(); i++) {
328: if (bExpr == null) {
329: bExpr = expr.eq(this .expressionList
330: .getExpression(i));
331: } else {
332: bExpr = bExpr.and(expr.eq(this .expressionList
333: .getExpression(i)));
334: }
335: }
336: bExpr = new BooleanExpression(OP_NOT, bExpr
337: .encloseWithInParentheses());
338: } else if (literalIsValidForSimpleComparison(expr)) {
339: if (this .expressionList.size() > 1) {
340: // More than 1 value to compare with a literal!
341: bExpr = super .noteq(expr);
342: } else {
343: // Just do a direct comparison with the basic literals
344: bExpr = new BooleanExpression(this , OP_NOTEQ, expr);
345: }
346: } else if (expr instanceof ObjectLiteral) {
347: bExpr = expr.noteq(this );
348: } else if (expr instanceof ObjectExpression) {
349: for (int i = 0; i < this .expressionList.size(); i++) {
350: ScalarExpression source = this .expressionList
351: .getExpression(i);
352: ScalarExpression target = expr.expressionList
353: .getExpression(i);
354: if (bExpr == null) {
355: bExpr = source.eq(target);
356: } else {
357: bExpr = bExpr.and(source.eq(target));
358: }
359: }
360: bExpr = new BooleanExpression(OP_NOT, bExpr
361: .encloseWithInParentheses());
362: } else if (expr instanceof UnboundVariable) {
363: if (((UnboundVariable) expr).getVariableType() == null) {
364: // Set the variable type to this objects type
365: ((UnboundVariable) expr).setVariableType(qs
366: .getClassLoaderResolver().classForName(
367: fieldType));
368: }
369: bExpr = expr.noteq(this );
370: } else if (expr instanceof BooleanBitColumnExpression) {
371: if (conditionExpr != null) {
372: bExpr = new BooleanExpression(ScalarExpression.OP_NOT,
373: conditionExpr);
374: }
375: } else {
376: bExpr = super .noteq(expr);
377: }
378:
379: if (conditionExpr != null) {
380: return new BooleanExpression(conditionExpr, OP_AND, bExpr);
381: }
382:
383: return bExpr;
384: }
385:
386: /**
387: * Convenience method to return if this object is valid for simple comparison
388: * with the passed expression. Performs a type comparison of the object and the expression
389: * for compatibility. The expression must be a literal of a suitable type for simple
390: * comparison (e.g where this object is a String, and the literal is a StringLiteral).
391: * @param expr The expression
392: * @return Whether a simple comparison is valid
393: */
394: private boolean literalIsValidForSimpleComparison(
395: ScalarExpression expr) {
396: // Our mapping is a single field type and is of the same basic type as the expression
397: if ((expr instanceof BooleanLiteral && (mapping instanceof BooleanMapping))
398: || (expr instanceof ByteLiteral && (mapping instanceof ByteMapping))
399: || (expr instanceof CharacterLiteral && (mapping instanceof CharacterMapping))
400: || (expr instanceof FloatingPointLiteral && (mapping instanceof FloatMapping
401: || mapping instanceof DoubleMapping || mapping instanceof BigDecimalMapping))
402: || (expr instanceof IntegerLiteral
403: && (mapping instanceof IntegerMapping
404: || mapping instanceof LongMapping || mapping instanceof BigIntegerMapping) || mapping instanceof ShortMapping)
405: || (expr instanceof SqlDateLiteral && (mapping instanceof SqlDateMapping))
406: || (expr instanceof SqlTimeLiteral && (mapping instanceof SqlTimeMapping))
407: || (expr instanceof SqlTimestampLiteral && (mapping instanceof SqlTimestampMapping))
408: || (expr instanceof StringLiteral && (mapping instanceof StringMapping || mapping instanceof CharacterMapping))) {
409: return true;
410: }
411:
412: return false;
413: }
414:
415: public BooleanExpression in(ScalarExpression expr) {
416: return new BooleanExpression(this , OP_IN, expr);
417: }
418:
419: /**
420: * Access a field in the object that this expression represents.
421: * If the field is contained in a different table then will use the "innerJoin" input parameter
422: * and make a join to the required table. If the field is a 1-1 relation and the current table holds the FK
423: * then no join will be made.
424: * @param subfieldName the field to be accessed in this object
425: * @param innerJoin whether to inner join
426: * @return The field expression representing the required field of this object
427: */
428: public ScalarExpression accessField(String subfieldName,
429: boolean innerJoin) {
430: DatastoreContainerObject table;
431: ClassLoaderResolver clr = qs.getClassLoaderResolver();
432: try {
433: if (mapping instanceof EmbeddedMapping) {
434: // Any embedded fields can go straight to the main table if embedded there
435: table = mapping.getDatastoreContainer();
436: if (te.getMainTable().equals(table)) {
437: // Provide the full field name so we can allow for nested embeddings
438: return te.newFieldExpression(fieldName + "."
439: + subfieldName);
440: }
441: } else if (mapping instanceof PersistenceCapableMapping
442: || mapping instanceof ReferenceMapping) {
443: AbstractClassMetaData otherCmd = qs.getStoreManager()
444: .getMetaDataManager().getMetaDataForClass(
445: mapping.getType(), clr);
446: if (otherCmd.getInheritanceMetaData()
447: .getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE) {
448: // Field is a PC class that uses "subclass-table" inheritance strategy (and so has multiple possible tables to join to)
449: AbstractClassMetaData[] cmds = qs.getStoreManager()
450: .getClassesManagingTableForClass(otherCmd,
451: clr);
452: if (cmds != null) {
453: // Join to the first table
454: // TODO Allow for all possible tables. Can we do an OR of the tables ? How ?
455: if (cmds.length > 1) {
456: JPOXLogger.QUERY.warn(LOCALISER.msg(
457: "037006", mapping
458: .getFieldMetaData()
459: .getFullFieldName(),
460: cmds[0].getFullClassName()));
461: }
462: table = qs.getStoreManager().getDatastoreClass(
463: cmds[0].getFullClassName(), clr);
464: } else {
465: // No subclasses with tables to join to, so throw a user error
466: throw new JPOXUserException(LOCALISER.msg(
467: "037005", mapping.getFieldMetaData()
468: .getFullFieldName()));
469: }
470: } else {
471: // Class of the field will have its own table
472: table = qs.getStoreManager().getDatastoreClass(
473: mapping.getType(), clr);
474: ApiAdapter api = qs.getStoreManager()
475: .getApiAdapter();
476:
477: if (fieldName != null && subfieldName != null) {
478: AbstractMemberMetaData subfieldMetaData = otherCmd
479: .getMetaDataForMember(subfieldName);
480: if (subfieldMetaData != null
481: && subfieldMetaData.isPrimaryKey()
482: && !api.isPersistable(subfieldMetaData
483: .getType())) {
484: // Selecting a non-PC field in the other class that is part of its PK mapping (so we have a column here for it)
485: // Avoids the extra join to the other table
486: JavaTypeMapping[] subMappings = ((PersistenceCapableMapping) mapping)
487: .getJavaTypeMapping();
488: if (subMappings.length == 1) {
489: // TODO Cater for a field of a composite PK being selected
490: return subMappings[0]
491: .newScalarExpression(qs, te);
492: }
493: }
494: }
495: }
496: } else {
497: table = qs.getStoreManager().getDatastoreClass(
498: mapping.getType(), clr);
499: }
500: } catch (ClassNotPersistenceCapableException cnpce) {
501: return te.newFieldExpression(subfieldName);
502: }
503:
504: if (fieldType != null && !fieldType.equals(mapping.getType())) {
505: // The field relation is to a table that allows multiple types to be stored (and has a discriminator)
506: // and the type we want is not the base type, so we need to restrict the values of the discriminator.
507: DiscriminatorMetaData dismd = table
508: .getDiscriminatorMetaData();
509: DiscriminatorMapping discriminatorMapping = (DiscriminatorMapping) table
510: .getDiscriminatorMapping(false);
511: if (dismd != null
512: && dismd.getStrategy() != DiscriminatorStrategy.NONE) {
513: // Start with the required class
514: BooleanExpression discrExpr = booleanConditionForClassInDiscriminator(
515: qs, fieldType, dismd, discriminatorMapping, te);
516:
517: // Add "or" condition for any of its possible subclasses (if any)
518: Iterator subclassIter = qs.getStoreManager()
519: .getSubClassesForClass(fieldType, true, clr)
520: .iterator();
521: while (subclassIter.hasNext()) {
522: String subCandidateType = (String) subclassIter
523: .next();
524: discrExpr
525: .ior(booleanConditionForClassInDiscriminator(
526: qs, subCandidateType, dismd,
527: discriminatorMapping, te));
528: }
529:
530: discrExpr.encloseWithInParentheses();
531:
532: // Add the discriminator restrictions as an "and" condition to the Query Statement
533: qs.andCondition(discrExpr);
534: }
535: }
536:
537: if (te.getMainTable().equals(table) && usingRelatedTable
538: && fieldName == null) {
539: //TODO fieldname==null, QUESTION is it "<candidateAlias>" namespace? debug and see
540: // We are already in the same table (left outer join in the constructor) and it isn't a self reference so just return
541: // the field expression. This can happen when we have a 1-1 bidir single FK and to generate the ObjectExpression we
542: // had to join across to the related field
543: return te.newFieldExpression(subfieldName);
544: }
545:
546: // jt... = "joined table"
547: String jtIdentifier = te.getAlias().getIdentifier();
548: if (fieldName != null) {
549: jtIdentifier += '.' + fieldName;
550: }
551: if (!subfieldName.equals("this")) // TODO Use qs.getCandidateAlias()
552: {
553: jtIdentifier += '.' + subfieldName;
554: }
555:
556: if (castType != null
557: && !castType.getName().equals(mapping.getType())) {
558: String castTypeName = castType.getName();
559: jtIdentifier += '.' + castTypeName.substring(castTypeName
560: .lastIndexOf('.') + 1);
561: }
562:
563: DatastoreIdentifier jtRangeVar = qs.getStoreManager()
564: .getIdentifierFactory().newIdentifier(
565: IdentifierFactory.TABLE, jtIdentifier);
566: LogicSetExpression jtTblExpr = qs
567: .getTableExpression(jtRangeVar);
568: if (jtTblExpr == null) {
569: // We can't join further (this subfield is not an object with a table expression)
570: if (te.getAlias().getIdentifier().equalsIgnoreCase("this")
571: && // TODO Use qs.getCandidateAlias()
572: table == qs.getMainTableExpression().getMainTable()
573: && fieldName == null) {
574: // Query contains "this.field" so just provide the associated field expression for that field
575: return qs.getMainTableExpression().newFieldExpression(
576: subfieldName);
577: }
578:
579: jtTblExpr = qs.newTableExpression(table, jtRangeVar);
580:
581: ScalarExpression jtExpr = table.getIDMapping()
582: .newScalarExpression(qs, jtTblExpr);
583: ScalarExpression expr = mapping.newScalarExpression(qs, te);
584: if (mapping.isNullable()) {
585: if (innerJoin) {
586: qs.innerJoin(jtExpr, expr, jtTblExpr, true, true);
587: } else {
588: qs.leftOuterJoin(jtExpr, expr, jtTblExpr, true,
589: true);
590: }
591: } else {
592: qs.innerJoin(jtExpr, expr, jtTblExpr, true, true);
593: }
594: }
595:
596: if (mapping instanceof EmbeddedPCMapping) {
597: return jtTblExpr.newFieldExpression(fieldName + "."
598: + subfieldName);
599: } else {
600: return jtTblExpr.newFieldExpression(subfieldName);
601: }
602: }
603:
604: /**
605: * Method to return a constraint for restricting the field to just instances of a particular class.
606: * @param expr Expression for the class that we want instances of (a ClassExpression).
607: * @return The expression for the instanceof clause
608: */
609: public BooleanExpression instanceOf(ScalarExpression expr) {
610: if (expr instanceof ClassExpression) {
611: ClassLoaderResolver clr = qs.getClassLoaderResolver();
612: Class instanceof Class = ((ClassExpression) expr).getCls();
613: Class fieldClass = clr.classForName(mapping.getType());
614: if (!fieldClass.isAssignableFrom(instanceof Class)
615: && !instanceof Class.isAssignableFrom(fieldClass)) {
616: // Field type and instanceof type are totally incompatible, so just return false
617: return new BooleanLiteral(qs, mapping, true)
618: .eq(new BooleanLiteral(qs, mapping, false));
619: }
620:
621: DatastoreContainerObject table;
622: try {
623: if (mapping instanceof EmbeddedMapping) {
624: // Field is embedded in this table
625: // TODO Enable instanceof on non-PC fields (currently just return "true")
626: return new BooleanLiteral(qs, mapping, true)
627: .eq(new BooleanLiteral(qs, mapping, true));
628: } else if (mapping instanceof PersistenceCapableMapping
629: || mapping instanceof ReferenceMapping) {
630: // Field has its own table, so join to it
631: AbstractClassMetaData fieldCmd = qs
632: .getStoreManager()
633: .getMetaDataManager()
634: .getMetaDataForClass(mapping.getType(), clr);
635: if (fieldCmd.getInheritanceMetaData()
636: .getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE) {
637: // Field is a PC class that uses "subclass-table" inheritance strategy (and so has multiple possible tables to join to)
638: AbstractClassMetaData[] cmds = qs
639: .getStoreManager()
640: .getClassesManagingTableForClass(
641: fieldCmd, clr);
642: if (cmds != null) {
643: // Join to the first table
644: // TODO Allow for all possible tables. Can we do an OR of the tables ? How ?
645: if (cmds.length > 1) {
646: JPOXLogger.QUERY.warn(LOCALISER.msg(
647: "037006", mapping
648: .getFieldMetaData()
649: .getFullFieldName(),
650: cmds[0].getFullClassName()));
651: }
652: table = qs.getStoreManager()
653: .getDatastoreClass(
654: cmds[0].getFullClassName(),
655: clr);
656: } else {
657: // No subclasses with tables to join to, so throw a user error
658: throw new JPOXUserException(LOCALISER.msg(
659: "037005", mapping
660: .getFieldMetaData()
661: .getFullFieldName()));
662: }
663: } else {
664: // Class of the field will have its own table
665: table = qs.getStoreManager().getDatastoreClass(
666: mapping.getType(), clr);
667: }
668: } else {
669: table = qs.getStoreManager().getDatastoreClass(
670: mapping.getType(), clr);
671: }
672: } catch (ClassNotPersistenceCapableException cnpce) {
673: // Field is not PersistenceCapable
674: // TODO Enable instanceof on non-PC fields (currently just return "true")
675: return new BooleanLiteral(qs, mapping, true)
676: .eq(new BooleanLiteral(qs, mapping, true));
677: }
678:
679: // Check if the table of the field has a discriminator
680: IdentifierFactory idFactory = qs.getStoreManager()
681: .getIdentifierFactory();
682: DiscriminatorMetaData dismd = table
683: .getDiscriminatorMetaData();
684: DiscriminatorMapping discriminatorMapping = (DiscriminatorMapping) table
685: .getDiscriminatorMapping(false);
686: if (discriminatorMapping != null) {
687: // Has a discriminator so do a join to the table of the field and apply a constraint on its discriminator
688: LogicSetExpression fieldTblExpr = null;
689: if (fieldName == null) {
690: // Using THIS so use default table expression
691: fieldTblExpr = qs.getMainTableExpression();
692: } else {
693: // Using field, so our real table will have an identifier of "THIS_{fieldName}" via INNER JOIN
694: String fieldIdentifier = te.getAlias()
695: .getIdentifier();
696: fieldIdentifier += '.' + fieldName;
697: DatastoreIdentifier fieldRangeVar = idFactory
698: .newIdentifier(IdentifierFactory.TABLE,
699: fieldIdentifier);
700: fieldTblExpr = qs.getTableExpression(fieldRangeVar);
701: if (fieldTblExpr == null) {
702: fieldTblExpr = qs.newTableExpression(table,
703: fieldRangeVar);
704: }
705: ScalarExpression fieldExpr = table.getIDMapping()
706: .newScalarExpression(qs, fieldTblExpr);
707: expr = mapping.newScalarExpression(qs, te);
708: qs.innerJoin(fieldExpr, expr, fieldTblExpr, true,
709: true);
710: }
711:
712: // Return a constraint on the discriminator for this table to get the right instances
713: // This allows all discriminator values for the instanceof class and all of its subclasses
714: // DISCRIM = 'baseVal' OR DISCRIM = 'sub1Val' OR DISCRIM = 'sub2Val' ... etc
715: BooleanExpression discrExpr = booleanConditionForClassInDiscriminator(
716: qs, instanceof Class.getName(), dismd,
717: discriminatorMapping, fieldTblExpr);
718: Iterator subclassIter = qs.getStoreManager()
719: .getSubClassesForClass(
720: instanceof Class.getName(), true, clr)
721: .iterator();
722: while (subclassIter.hasNext()) {
723: String subCandidateType = (String) subclassIter
724: .next();
725: discrExpr
726: .ior(booleanConditionForClassInDiscriminator(
727: qs, subCandidateType, dismd,
728: discriminatorMapping, fieldTblExpr));
729: }
730: discrExpr.encloseWithInParentheses();
731:
732: return discrExpr;
733: } else {
734: // No discriminator so maybe union, or just a SELECT
735: // Need to join to the instanceof class (where appropriate)
736: // TODO RDBMS-71 Only join on the UNION select that it is applicable to
737: if (table instanceof DatastoreClass) {
738: DatastoreClass ct = (DatastoreClass) table;
739: if (ct.managesClass(instanceof Class.getName())) {
740: // This type is managed in this table so must be an instance
741: return new BooleanLiteral(qs, mapping, true)
742: .eq(new BooleanLiteral(qs, mapping,
743: true));
744: } else {
745: // The instanceof type is not managed here
746: DatastoreClass instanceof Table = qs
747: .getStoreManager().getDatastoreClass(
748: instanceof Class.getName(), clr);
749: String fieldIdentifier = te.getAlias()
750: .getIdentifier();
751: if (fieldName == null) {
752: // Using THIS, so our real table will have an identifier of "THIS_INST"
753: fieldIdentifier += ".INST";
754: } else {
755: // Using field, so our real table will have an identifier of "THIS_{fieldName}"
756: fieldIdentifier += '.' + fieldName;
757: }
758: DatastoreIdentifier fieldRangeVar = idFactory
759: .newIdentifier(IdentifierFactory.TABLE,
760: fieldIdentifier);
761: LogicSetExpression fieldTblExpr = qs
762: .newTableExpression(instanceof Table,
763: fieldRangeVar);
764: ScalarExpression fieldExpr = table
765: .getIDMapping().newScalarExpression(qs,
766: te);
767: if (fieldName == null) {
768: expr = instanceof Table.getIDMapping()
769: .newScalarExpression(qs,
770: fieldTblExpr);
771: } else {
772: expr = mapping.newScalarExpression(qs,
773: fieldTblExpr);
774: }
775: qs.innerJoin(fieldExpr, expr, fieldTblExpr,
776: true, true);
777: return new BooleanLiteral(qs, mapping, true)
778: .eq(new BooleanLiteral(qs, mapping,
779: true));
780: }
781: } else {
782: // Assumed to be in the right class
783: return new BooleanLiteral(qs, mapping, true)
784: .eq(new BooleanLiteral(qs, mapping, true));
785: }
786: }
787: } else {
788: // Invalid to use "XX instanceof YY" where YY is not a class.
789: throw new JPOXUserException(LOCALISER.msg("037007", expr
790: .getClass().getName()));
791: }
792: }
793:
794: /**
795: * Set the field which this expression was created from.
796: * @param fieldName The fieldName to set.
797: * @param fieldType The fieldType to set
798: */
799: public void setFieldDefinition(String fieldName, String fieldType) {
800: this .fieldName = fieldName;
801: this .fieldType = fieldType;
802: }
803:
804: /**
805: * Convenience method to generate a BooleanExpression for a value of the
806: * discriminator for the provided table expression.
807: * @param stmt The Query Statement to be updated
808: * @param className The possible class name
809: * @param dismd MetaData for the discriminator
810: * @param discriminatorMapping Mapping for the discriminator
811: * @param tableExpr Table Expression
812: * @return BooleanExpression for this discriminator value
813: */
814: private BooleanExpression booleanConditionForClassInDiscriminator(
815: QueryExpression stmt, String className,
816: DiscriminatorMetaData dismd,
817: JavaTypeMapping discriminatorMapping,
818: LogicSetExpression tableExpr) {
819: // Default to the "class-name" discriminator strategy
820: String discriminatorValue = className;
821: if (dismd.getStrategy() == DiscriminatorStrategy.VALUE_MAP) {
822: // Get the MetaData for the target class since that holds the "value"
823: AbstractClassMetaData targetCmd = stmt.getStoreManager()
824: .getMetaDataManager().getMetaDataForClass(
825: className, stmt.getClassLoaderResolver());
826: discriminatorValue = targetCmd.getInheritanceMetaData()
827: .getDiscriminatorMetaData().getValue();
828: }
829: ScalarExpression discrExpr = discriminatorMapping
830: .newScalarExpression(stmt, tableExpr);
831: ScalarExpression discrVal = discriminatorMapping.newLiteral(
832: stmt, discriminatorValue);
833: return discrExpr.eq(discrVal);
834: }
835: }
|