View Javadoc

1   package org.apache.ojb.broker.accesslayer;
2   
3   /* Copyright 2003-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.lang.reflect.Array;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import org.apache.ojb.broker.Identity;
27  import org.apache.ojb.broker.ManageableCollection;
28  import org.apache.ojb.broker.PersistenceBroker;
29  import org.apache.ojb.broker.accesslayer.conversions.FieldConversion;
30  import org.apache.ojb.broker.core.PersistenceBrokerImpl;
31  import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
32  import org.apache.ojb.broker.metadata.ClassDescriptor;
33  import org.apache.ojb.broker.metadata.CollectionDescriptor;
34  import org.apache.ojb.broker.metadata.FieldDescriptor;
35  import org.apache.ojb.broker.metadata.FieldHelper;
36  import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
37  import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
38  import org.apache.ojb.broker.query.Criteria;
39  import org.apache.ojb.broker.query.Query;
40  import org.apache.ojb.broker.query.QueryByMtoNCriteria;
41  import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
42  
43  /**
44   * Relationship Prefetcher for MtoN-Collections.
45   * 
46   * @author <a href="mailto:jbraeuchi@gmx.ch">Jakob Braeuchi</a>
47   * @version $Id: MtoNCollectionPrefetcher.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
48   */
49  public class MtoNCollectionPrefetcher extends CollectionPrefetcher
50  {
51  
52      /**
53       * @param aBroker the PersistenceBroker
54       * @param anOrd the CollectionDescriptor
55       */
56      public MtoNCollectionPrefetcher(PersistenceBrokerImpl aBroker, ObjectReferenceDescriptor anOrd)
57      {
58          super(aBroker, anOrd);
59      }
60  
61      /**
62       * @see org.apache.ojb.broker.accesslayer.RelationshipPrefetcher#prefetchRelationship(Collection)
63       */
64      public void prefetchRelationship(Collection owners)
65      {
66          Query[] queries;
67          Query[] mnQueries;
68          Collection children = new ArrayList();
69          Collection mnImplementors = new ArrayList();
70  
71          queries = buildPrefetchQueries(owners, children);
72          mnQueries = buildMtoNImplementorQueries(owners, children);
73  
74          for (int i = 0; i < queries.length; i++)
75          {
76              Iterator iter = getBroker().getIteratorByQuery(queries[i]);
77              while (iter.hasNext())
78              {
79                  Object aChild = iter.next();
80  
81                  // BRJ: simulate the distinct removed from the query
82                  if (!children.contains(aChild))
83                  {
84                      children.add(aChild);
85                  }
86              }
87  
88              Iterator mnIter = getBroker().getReportQueryIteratorByQuery(mnQueries[i]);
89              while (mnIter.hasNext())
90              {
91                  mnImplementors.add(mnIter.next());
92              }
93          }
94  
95          associateBatched(owners, children, mnImplementors);
96      }
97  
98      /**
99       * 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 }