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