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    }