001: package org.apache.ojb.broker.accesslayer;
002:
003: /* Copyright 2003-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.lang.reflect.Array;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import org.apache.ojb.broker.Identity;
027: import org.apache.ojb.broker.ManageableCollection;
028: import org.apache.ojb.broker.PersistenceBroker;
029: import org.apache.ojb.broker.accesslayer.conversions.FieldConversion;
030: import org.apache.ojb.broker.core.PersistenceBrokerImpl;
031: import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
032: import org.apache.ojb.broker.metadata.ClassDescriptor;
033: import org.apache.ojb.broker.metadata.CollectionDescriptor;
034: import org.apache.ojb.broker.metadata.FieldDescriptor;
035: import org.apache.ojb.broker.metadata.FieldHelper;
036: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
037: import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
038: import org.apache.ojb.broker.query.Criteria;
039: import org.apache.ojb.broker.query.Query;
040: import org.apache.ojb.broker.query.QueryByMtoNCriteria;
041: import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
042:
043: /**
044: * Relationship Prefetcher for MtoN-Collections.
045: *
046: * @author <a href="mailto:jbraeuchi@gmx.ch">Jakob Braeuchi</a>
047: * @version $Id: MtoNCollectionPrefetcher.java,v 1.12.2.7 2005/12/21 22:22:58 tomdz Exp $
048: */
049: public class MtoNCollectionPrefetcher extends CollectionPrefetcher {
050:
051: /**
052: * @param aBroker the PersistenceBroker
053: * @param anOrd the CollectionDescriptor
054: */
055: public MtoNCollectionPrefetcher(PersistenceBrokerImpl aBroker,
056: ObjectReferenceDescriptor anOrd) {
057: super (aBroker, anOrd);
058: }
059:
060: /**
061: * @see org.apache.ojb.broker.accesslayer.RelationshipPrefetcher#prefetchRelationship(Collection)
062: */
063: public void prefetchRelationship(Collection owners) {
064: Query[] queries;
065: Query[] mnQueries;
066: Collection children = new ArrayList();
067: Collection mnImplementors = new ArrayList();
068:
069: queries = buildPrefetchQueries(owners, children);
070: mnQueries = buildMtoNImplementorQueries(owners, children);
071:
072: for (int i = 0; i < queries.length; i++) {
073: Iterator iter = getBroker().getIteratorByQuery(queries[i]);
074: while (iter.hasNext()) {
075: Object aChild = iter.next();
076:
077: // BRJ: simulate the distinct removed from the query
078: if (!children.contains(aChild)) {
079: children.add(aChild);
080: }
081: }
082:
083: Iterator mnIter = getBroker()
084: .getReportQueryIteratorByQuery(mnQueries[i]);
085: while (mnIter.hasNext()) {
086: mnImplementors.add(mnIter.next());
087: }
088: }
089:
090: associateBatched(owners, children, mnImplementors);
091: }
092:
093: /**
094: * Build the prefetch query for a M-N relationship, The query looks like the following sample :
095: * <br>
096: * <pre>
097: * crit = new Criteria();
098: * crit.addIn("PERSON_PROJECT.PROJECT_ID", ids);
099: * crit.addEqualToField("id","PERSON_PROJECT.PERSON_ID");
100: * qry = new QueryByMtoNCriteria(Person.class, "PERSON_PROJECT", crit, true);
101: * </pre>
102: *
103: * @param ids Collection containing all identities of objects of the M side
104: * @return the prefetch Query
105: */
106: protected Query buildPrefetchQuery(Collection ids) {
107: CollectionDescriptor cds = getCollectionDescriptor();
108: String[] indFkCols = getFksToThisClass();
109: String[] indItemFkCols = getFksToItemClass();
110: FieldDescriptor[] itemPkFields = getItemClassDescriptor()
111: .getPkFields();
112:
113: Criteria crit = buildPrefetchCriteria(ids, indFkCols,
114: indItemFkCols, itemPkFields);
115:
116: // BRJ: do not use distinct:
117: //
118: // ORA-22901 cannot compare nested table or VARRAY or LOB attributes of an object type
119: // Cause: Comparison of nested table or VARRAY or LOB attributes of an
120: // object type was attempted in the absence of a MAP or ORDER method.
121: // Action: Define a MAP or ORDER method for the object type.
122: //
123: // Without the distinct the resultset may contain duplicate rows
124:
125: return new QueryByMtoNCriteria(cds.getItemClass(), cds
126: .getIndirectionTable(), crit, false);
127: }
128:
129: /**
130: * Build a query to read the mn-implementors
131: * @param ids
132: */
133: protected Query buildMtoNImplementorQuery(Collection ids) {
134: String[] indFkCols = getFksToThisClass();
135: String[] indItemFkCols = getFksToItemClass();
136: FieldDescriptor[] pkFields = getOwnerClassDescriptor()
137: .getPkFields();
138: FieldDescriptor[] itemPkFields = getItemClassDescriptor()
139: .getPkFields();
140: String[] cols = new String[indFkCols.length
141: + indItemFkCols.length];
142: int[] jdbcTypes = new int[indFkCols.length
143: + indItemFkCols.length];
144:
145: // concatenate the columns[]
146: System.arraycopy(indFkCols, 0, cols, 0, indFkCols.length);
147: System.arraycopy(indItemFkCols, 0, cols, indFkCols.length,
148: indItemFkCols.length);
149:
150: Criteria crit = buildPrefetchCriteria(ids, indFkCols,
151: indItemFkCols, itemPkFields);
152:
153: // determine the jdbcTypes of the pks
154: for (int i = 0; i < pkFields.length; i++) {
155: jdbcTypes[i] = pkFields[i].getJdbcType().getType();
156: }
157: for (int i = 0; i < itemPkFields.length; i++) {
158: jdbcTypes[pkFields.length + i] = itemPkFields[i]
159: .getJdbcType().getType();
160: }
161:
162: ReportQueryByMtoNCriteria q = new ReportQueryByMtoNCriteria(
163: getItemClassDescriptor().getClassOfObject(), cols,
164: crit, false);
165: q.setIndirectionTable(getCollectionDescriptor()
166: .getIndirectionTable());
167: q.setJdbcTypes(jdbcTypes);
168:
169: CollectionDescriptor cds = getCollectionDescriptor();
170: //check if collection must be ordered
171: if (!cds.getOrderBy().isEmpty()) {
172: Iterator iter = cds.getOrderBy().iterator();
173: while (iter.hasNext()) {
174: q.addOrderBy((FieldHelper) iter.next());
175: }
176: }
177:
178: return q;
179: }
180:
181: /**
182: * prefix the this class fk columns with the indirection table
183: */
184: private String[] getFksToThisClass() {
185: String indTable = getCollectionDescriptor()
186: .getIndirectionTable();
187: String[] fks = getCollectionDescriptor().getFksToThisClass();
188: String[] result = new String[fks.length];
189:
190: for (int i = 0; i < result.length; i++) {
191: result[i] = indTable + "." + fks[i];
192: }
193:
194: return result;
195: }
196:
197: /**
198: * prefix the item class fk columns with the indirection table
199: */
200: private String[] getFksToItemClass() {
201: String indTable = getCollectionDescriptor()
202: .getIndirectionTable();
203: String[] fks = getCollectionDescriptor().getFksToItemClass();
204: String[] result = new String[fks.length];
205:
206: for (int i = 0; i < result.length; i++) {
207: result[i] = indTable + "." + fks[i];
208: }
209:
210: return result;
211: }
212:
213: /**
214: * Build the multiple queries for one relationship because of limitation of IN(...)
215: *
216: * @param owners Collection containing all objects of the ONE side
217: */
218: protected Query[] buildMtoNImplementorQueries(Collection owners,
219: Collection children) {
220: ClassDescriptor cld = getOwnerClassDescriptor();
221: PersistenceBroker pb = getBroker();
222: //Class topLevelClass = pb.getTopLevelClass(cld.getClassOfObject());
223: //BrokerHelper helper = pb.serviceBrokerHelper();
224: Collection queries = new ArrayList(owners.size());
225: Collection idsSubset = new HashSet(owners.size());
226: //Object[] fkValues;
227: Object owner;
228: Identity id;
229:
230: Iterator iter = owners.iterator();
231: while (iter.hasNext()) {
232: owner = iter.next();
233: id = pb.serviceIdentity().buildIdentity(cld, owner);
234: idsSubset.add(id);
235: if (idsSubset.size() == pkLimit) {
236: queries.add(buildMtoNImplementorQuery(idsSubset));
237: idsSubset.clear();
238: }
239: }
240:
241: if (idsSubset.size() > 0) {
242: queries.add(buildMtoNImplementorQuery(idsSubset));
243: }
244:
245: return (Query[]) queries.toArray(new Query[queries.size()]);
246: }
247:
248: /**
249: * Build the prefetch criteria
250: *
251: * @param ids Collection of identities of M side
252: * @param fkCols indirection table fks to this class
253: * @param itemFkCols indirection table fks to item class
254: * @param itemPkFields
255: */
256: private Criteria buildPrefetchCriteria(Collection ids,
257: String[] fkCols, String[] itemFkCols,
258: FieldDescriptor[] itemPkFields) {
259: if (fkCols.length == 1 && itemFkCols.length == 1) {
260: return buildPrefetchCriteriaSingleKey(ids, fkCols[0],
261: itemFkCols[0], itemPkFields[0]);
262: } else {
263: return buildPrefetchCriteriaMultipleKeys(ids, fkCols,
264: itemFkCols, itemPkFields);
265: }
266:
267: }
268:
269: /**
270: * Build the prefetch criteria
271: *
272: * @param ids Collection of identities of M side
273: * @param fkCol indirection table fks to this class
274: * @param itemFkCol indirection table fks to item class
275: * @param itemPkField
276: * @return the Criteria
277: */
278: private Criteria buildPrefetchCriteriaSingleKey(Collection ids,
279: String fkCol, String itemFkCol, FieldDescriptor itemPkField) {
280: Criteria crit = new Criteria();
281: ArrayList values = new ArrayList(ids.size());
282: Iterator iter = ids.iterator();
283: Identity id;
284:
285: while (iter.hasNext()) {
286: id = (Identity) iter.next();
287: values.add(id.getPrimaryKeyValues()[0]);
288: }
289:
290: switch (values.size()) {
291: case 0:
292: break;
293: case 1:
294: crit.addEqualTo(fkCol, values.get(0));
295: break;
296: default:
297: // create IN (...) for the single key field
298: crit.addIn(fkCol, values);
299: break;
300: }
301:
302: crit.addEqualToField(itemPkField.getAttributeName(), itemFkCol);
303:
304: return crit;
305: }
306:
307: /**
308: * Build the prefetch criteria
309: *
310: * @param ids Collection of identities of M side
311: * @param fkCols indirection table fks to this class
312: * @param itemFkCols indirection table fks to item class
313: * @param itemPkFields
314: * @return the Criteria
315: */
316: private Criteria buildPrefetchCriteriaMultipleKeys(Collection ids,
317: String[] fkCols, String[] itemFkCols,
318: FieldDescriptor[] itemPkFields) {
319: Criteria crit = new Criteria();
320: Criteria critValue = new Criteria();
321: Iterator iter = ids.iterator();
322:
323: for (int i = 0; i < itemPkFields.length; i++) {
324: crit.addEqualToField(itemPkFields[i].getAttributeName(),
325: itemFkCols[i]);
326: }
327:
328: while (iter.hasNext()) {
329: Criteria c = new Criteria();
330: Identity id = (Identity) iter.next();
331: Object[] val = id.getPrimaryKeyValues();
332:
333: for (int i = 0; i < val.length; i++) {
334:
335: if (val[i] == null) {
336: c.addIsNull(fkCols[i]);
337: } else {
338: c.addEqualTo(fkCols[i], val[i]);
339: }
340:
341: }
342:
343: critValue.addOrCriteria(c);
344: }
345:
346: crit.addAndCriteria(critValue);
347: return crit;
348: }
349:
350: /**
351: * Answer the FieldConversions for the PkFields
352: * @param cld
353: * @return the pk FieldConversions
354: */
355: private FieldConversion[] getPkFieldConversion(ClassDescriptor cld) {
356: FieldDescriptor[] pks = cld.getPkFields();
357: FieldConversion[] fc = new FieldConversion[pks.length];
358:
359: for (int i = 0; i < pks.length; i++) {
360: fc[i] = pks[i].getFieldConversion();
361: }
362:
363: return fc;
364: }
365:
366: /**
367: * Convert the Values using the FieldConversion.sqlToJava
368: * @param fcs
369: * @param values
370: */
371: private Object[] convert(FieldConversion[] fcs, Object[] values) {
372: Object[] convertedValues = new Object[values.length];
373:
374: for (int i = 0; i < values.length; i++) {
375: convertedValues[i] = fcs[i].sqlToJava(values[i]);
376: }
377:
378: return convertedValues;
379: }
380:
381: /**
382: * associate the batched Children with their owner object loop over children
383: * <br><br>
384: * BRJ: There is a potential problem with the type of the pks used to build the Identities.
385: * When creating an Identity for the owner, the type of pk is defined by the instvars
386: * representing the pk. When creating the Identity based on the mToNImplementor the
387: * type of the pk is defined by the jdbc-type of field-descriptor of the referenced class.
388: * This type mismatch results in Identities not being equal.
389: * Integer[] {10,20,30} is not equal Long[] {10,20,30}
390: * <br><br>
391: * This problem is documented in defect OJB296.
392: * The conversion of the keys of the mToNImplementor should solve this problem.
393: */
394: protected void associateBatched(Collection owners,
395: Collection children, Collection mToNImplementors) {
396: CollectionDescriptor cds = getCollectionDescriptor();
397: PersistentField field = cds.getPersistentField();
398: PersistenceBroker pb = getBroker();
399: Class ownerTopLevelClass = pb
400: .getTopLevelClass(getOwnerClassDescriptor()
401: .getClassOfObject());
402: Class childTopLevelClass = pb
403: .getTopLevelClass(getItemClassDescriptor()
404: .getClassOfObject());
405: Class collectionClass = cds.getCollectionClass(); // this collection type will be used:
406: HashMap childMap = new HashMap();
407: HashMap ownerIdsToLists = new HashMap();
408: FieldConversion[] ownerFc = getPkFieldConversion(getOwnerClassDescriptor());
409: FieldConversion[] childFc = getPkFieldConversion(getItemClassDescriptor());
410:
411: // initialize the owner list map
412: for (Iterator it = owners.iterator(); it.hasNext();) {
413: Object owner = it.next();
414: Identity oid = pb.serviceIdentity().buildIdentity(owner);
415: ownerIdsToLists.put(oid, new ArrayList());
416: }
417:
418: // build the children map
419: for (Iterator it = children.iterator(); it.hasNext();) {
420: Object child = it.next();
421: Identity oid = pb.serviceIdentity().buildIdentity(child);
422: childMap.put(oid, child);
423: }
424:
425: int ownerPkLen = getOwnerClassDescriptor().getPkFields().length;
426: int childPkLen = getItemClassDescriptor().getPkFields().length;
427: Object[] ownerPk = new Object[ownerPkLen];
428: Object[] childPk = new Object[childPkLen];
429:
430: // build list of children based on m:n implementors
431: for (Iterator it = mToNImplementors.iterator(); it.hasNext();) {
432: Object[] mToN = (Object[]) it.next();
433: System.arraycopy(mToN, 0, ownerPk, 0, ownerPkLen);
434: System.arraycopy(mToN, ownerPkLen, childPk, 0, childPkLen);
435:
436: // BRJ: apply the FieldConversions, OJB296
437: ownerPk = convert(ownerFc, ownerPk);
438: childPk = convert(childFc, childPk);
439:
440: Identity ownerId = pb.serviceIdentity().buildIdentity(null,
441: ownerTopLevelClass, ownerPk);
442: Identity childId = pb.serviceIdentity().buildIdentity(null,
443: childTopLevelClass, childPk);
444:
445: // Identities may not be equal due to type-mismatch
446: Collection list = (Collection) ownerIdsToLists.get(ownerId);
447: Object child = childMap.get(childId);
448: list.add(child);
449: }
450:
451: // connect children list to owners
452: for (Iterator it = owners.iterator(); it.hasNext();) {
453: Object result;
454: Object owner = it.next();
455: Identity ownerId = pb.serviceIdentity()
456: .buildIdentity(owner);
457:
458: List list = (List) ownerIdsToLists.get(ownerId);
459:
460: if ((collectionClass == null) && field.getType().isArray()) {
461: int length = list.size();
462: Class itemtype = field.getType().getComponentType();
463:
464: result = Array.newInstance(itemtype, length);
465:
466: for (int j = 0; j < length; j++) {
467: Array.set(result, j, list.get(j));
468: }
469: } else {
470: ManageableCollection col = createCollection(cds,
471: collectionClass);
472:
473: for (Iterator it2 = list.iterator(); it2.hasNext();) {
474: col.ojbAdd(it2.next());
475: }
476: result = col;
477: }
478:
479: Object value = field.get(owner);
480: if ((value instanceof CollectionProxyDefaultImpl)
481: && (result instanceof Collection)) {
482: ((CollectionProxyDefaultImpl) value)
483: .setData((Collection) result);
484: } else {
485: field.set(owner, result);
486: }
487: }
488:
489: }
490: }
|