001    package org.apache.ojb.broker.util;
002    
003    /* Copyright 2002-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.sql.PreparedStatement;
019    import java.sql.ResultSet;
020    import java.sql.SQLException;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.StringTokenizer;
027    
028    import org.apache.commons.collections.CollectionUtils;
029    import org.apache.commons.collections.iterators.ArrayIterator;
030    import org.apache.commons.collections.map.ReferenceIdentityMap;
031    import org.apache.ojb.broker.Identity;
032    import org.apache.ojb.broker.ManageableCollection;
033    import org.apache.ojb.broker.MtoNImplementor;
034    import org.apache.ojb.broker.OJBRuntimeException;
035    import org.apache.ojb.broker.PBKey;
036    import org.apache.ojb.broker.PersistenceBrokerException;
037    import org.apache.ojb.broker.accesslayer.StatementManagerIF;
038    import org.apache.ojb.broker.accesslayer.sql.SqlExistStatement;
039    import org.apache.ojb.broker.core.PersistenceBrokerImpl;
040    import org.apache.ojb.broker.core.ValueContainer;
041    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
042    import org.apache.ojb.broker.core.proxy.ProxyHelper;
043    import org.apache.ojb.broker.metadata.ClassDescriptor;
044    import org.apache.ojb.broker.metadata.CollectionDescriptor;
045    import org.apache.ojb.broker.metadata.FieldDescriptor;
046    import org.apache.ojb.broker.metadata.FieldHelper;
047    import org.apache.ojb.broker.metadata.MetadataException;
048    import org.apache.ojb.broker.metadata.MetadataManager;
049    import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
050    import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
051    import org.apache.ojb.broker.platforms.Platform;
052    import org.apache.ojb.broker.query.Criteria;
053    import org.apache.ojb.broker.query.MtoNQuery;
054    import org.apache.ojb.broker.query.Query;
055    import org.apache.ojb.broker.query.QueryByCriteria;
056    import org.apache.ojb.broker.query.QueryBySQL;
057    import org.apache.ojb.broker.query.ReportQueryByCriteria;
058    import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
059    import org.apache.ojb.broker.util.logging.LoggerFactory;
060    import org.apache.ojb.broker.util.sequence.SequenceManagerException;
061    
062    /**
063     * This class contains helper methods primarily used by the {@link org.apache.ojb.broker.PersistenceBroker}
064     * implementation (e.g. contains methods to assign the the values of 'autoincrement' fields).
065     * <br/>
066     * Furthermore it was used to introduce new features related to {@link org.apache.ojb.broker.PersistenceBroker} - these
067     * new features and services (if they stand the test of time) will be moved to separate services in future.
068     *
069     * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
070     * @version $Id: BrokerHelper.java,v 1.1 2007-08-24 22:17:36 ewestfal Exp $
071     */
072    public class BrokerHelper
073    {
074        public static final String REPOSITORY_NAME_SEPARATOR = "#";
075        private PersistenceBrokerImpl m_broker;
076    
077        public BrokerHelper(PersistenceBrokerImpl broker)
078        {
079            this.m_broker = broker;
080        }
081    
082        /**
083         * splits up the name string and extract db url,
084         * user name and password and build a new PBKey
085         * instance - the token '#' is used to separate
086         * the substrings.
087         * @throws PersistenceBrokerException if given name was <code>null</code>
088         */
089        public static PBKey extractAllTokens(String name)
090        {
091            if(name == null)
092            {
093                throw new PersistenceBrokerException("Could not extract PBKey, given argument is 'null'");
094            }
095            String user = null;
096            String passwd = null;
097            StringTokenizer tok = new StringTokenizer(name, REPOSITORY_NAME_SEPARATOR);
098            String dbName = tok.nextToken();
099            if(tok.hasMoreTokens())
100            {
101                user = tok.nextToken();
102                if(user != null && user.trim().equals(""))
103                {
104                    user = null;
105                }
106            }
107            if(tok.hasMoreTokens())
108            {
109                if(user != null)
110                    passwd = tok.nextToken();
111            }
112            if(user != null && passwd == null)
113            {
114                passwd = "";
115            }
116            return new PBKey(dbName, user, passwd);
117        }
118    
119        /**
120         * Check if the user of the given PBKey was <code>null</code>, if so we try to
121         * get user/password from the jdbc-connection-descriptor matching the given
122         * PBKey.getAlias().
123         */
124        public static PBKey crossCheckPBKey(PBKey key)
125        {
126            if(key.getUser() == null)
127            {
128                PBKey defKey = MetadataManager.getInstance().connectionRepository().getStandardPBKeyForJcdAlias(key.getAlias());
129                if(defKey != null)
130                {
131                    return defKey;
132                }
133            }
134            return key;
135        }
136    
137        /**
138         * Answer the real ClassDescriptor for anObj
139         * ie. aCld may be an Interface of anObj, so the cld for anObj is returned
140         */
141        private ClassDescriptor getRealClassDescriptor(ClassDescriptor aCld, Object anObj)
142        {
143            ClassDescriptor result;
144    
145            if(aCld.getClassOfObject() == ProxyHelper.getRealClass(anObj))
146            {
147                result = aCld;
148            }
149            else
150            {
151                result = aCld.getRepository().getDescriptorFor(anObj.getClass());
152            }
153    
154            return result;
155        }
156    
157        /**
158         * Returns an Array with an Objects PK VALUES if convertToSql is true, any
159         * associated java-to-sql conversions are applied. If the Object is a Proxy
160         * or a VirtualProxy NO conversion is necessary.
161         *
162         * @param objectOrProxy
163         * @param convertToSql
164         * @return Object[]
165         * @throws PersistenceBrokerException
166         */
167        public ValueContainer[] getKeyValues(ClassDescriptor cld, Object objectOrProxy, boolean convertToSql) throws PersistenceBrokerException
168        {
169            IndirectionHandler handler = ProxyHelper.getIndirectionHandler(objectOrProxy);
170    
171            if(handler != null)
172            {
173                return getKeyValues(cld, handler.getIdentity(), convertToSql);  //BRJ: convert Identity
174            }
175            else
176            {
177                ClassDescriptor realCld = getRealClassDescriptor(cld, objectOrProxy);
178                return getValuesForObject(realCld.getPkFields(), objectOrProxy, convertToSql);
179            }
180        }
181    
182        /**
183         * Return primary key values of given Identity object.
184         *
185         * @param cld
186         * @param oid
187         * @return Object[]
188         * @throws PersistenceBrokerException
189         */
190        public ValueContainer[] getKeyValues(ClassDescriptor cld, Identity oid) throws PersistenceBrokerException
191        {
192            return getKeyValues(cld, oid, true);
193        }
194    
195        /**
196         * Return key Values of an Identity
197         * @param cld
198         * @param oid
199         * @param convertToSql
200         * @return Object[]
201         * @throws PersistenceBrokerException
202         */
203        public ValueContainer[] getKeyValues(ClassDescriptor cld, Identity oid, boolean convertToSql) throws PersistenceBrokerException
204        {
205            FieldDescriptor[] pkFields = cld.getPkFields();
206            ValueContainer[] result = new ValueContainer[pkFields.length];
207            Object[] pkValues = oid.getPrimaryKeyValues();
208    
209            try
210            {
211                for(int i = 0; i < result.length; i++)
212                {
213                    FieldDescriptor fd = pkFields[i];
214                    Object cv = pkValues[i];
215                    if(convertToSql)
216                    {
217                        // BRJ : apply type and value mapping
218                        cv = fd.getFieldConversion().javaToSql(cv);
219                    }
220                    result[i] = new ValueContainer(cv, fd.getJdbcType());
221                }
222            }
223            catch(Exception e)
224            {
225                throw new PersistenceBrokerException("Can't generate primary key values for given Identity " + oid, e);
226            }
227            return result;
228        }
229    
230        /**
231         * returns an Array with an Objects PK VALUES, with any java-to-sql
232         * FieldConversion applied. If the Object is a Proxy or a VirtualProxy NO
233         * conversion is necessary.
234         *
235         * @param objectOrProxy
236         * @return Object[]
237         * @throws PersistenceBrokerException
238         */
239        public ValueContainer[] getKeyValues(ClassDescriptor cld, Object objectOrProxy) throws PersistenceBrokerException
240        {
241            return getKeyValues(cld, objectOrProxy, true);
242        }
243    
244        /**
245         * Decide if the given object value represents 'null'.<br/>
246         *
247         * - If given value is 'null' itself, true will be returned<br/>
248         *
249         * - If given value is instance of Number with value 0 and the field-descriptor
250         * represents a primitive field, true will be returned<br/>
251         *
252         * - If given value is instance of String with length 0 and the field-descriptor
253         * is a primary key, true will be returned<br/>
254         */
255        public boolean representsNull(FieldDescriptor fld, Object aValue)
256        {
257            if(aValue == null) return true;
258    
259            boolean result = false;
260            if(((aValue instanceof Number) && (((Number) aValue).longValue() == 0)))
261            {
262                Class type = fld.getPersistentField().getType();
263                /*
264                AnonymousPersistentFields will *always* have a null type according to the
265                javadoc comments in AnonymousPersistentField.getType() and never represents
266                a primitve java field with value 0, thus we return always 'false' in this case.
267                (If the value object is null, the first check above return true)
268                */
269                if(type != null)
270                {
271                    result = type.isPrimitive();
272                }
273            }
274            // TODO: Do we need this check?? String could be nullified, why should we assume
275            // it's 'null' on empty string?
276            else if((aValue instanceof String) && (((String) aValue).length() == 0))
277            {
278                result = fld.isPrimaryKey();
279            }
280            return result;
281        }
282    
283        /**
284         * Detect if the given object has a PK field represents a 'null' value.
285         */
286        public boolean hasNullPKField(ClassDescriptor cld, Object obj)
287        {
288            FieldDescriptor[] fields = cld.getPkFields();
289            boolean hasNull = false;
290            // an unmaterialized proxy object can never have nullified PK's
291            IndirectionHandler handler = ProxyHelper.getIndirectionHandler(obj);
292            if(handler == null || handler.alreadyMaterialized())
293            {
294                if(handler != null) obj = handler.getRealSubject();
295                FieldDescriptor fld;
296                for(int i = 0; i < fields.length; i++)
297                {
298                    fld = fields[i];
299                    hasNull = representsNull(fld, fld.getPersistentField().get(obj));
300                    if(hasNull) break;
301                }
302            }
303            return hasNull;
304        }
305    
306        /**
307         * Set an autoincremented value in given object field that has already
308         * had a field conversion run on it, if an value for the given field is
309         * already set, it will be overridden - no further checks are done.
310         * <p>
311         * The data type of the value that is returned by this method is
312         * compatible with the java-world.  The return value has <b>NOT</b>
313         * been run through a field conversion and converted to a corresponding
314         * sql-type.
315         *
316         * @return the autoincremented value set on given object
317         * @throws PersistenceBrokerException if there is an erros accessing obj field values
318         */
319        private Object setAutoIncrementValue(FieldDescriptor fd, Object obj)
320        {
321            PersistentField f = fd.getPersistentField();
322            try
323            {
324                // lookup SeqMan for a value matching db column an
325                Object result = m_broker.serviceSequenceManager().getUniqueValue(fd);
326                // reflect autoincrement value back into object
327                f.set(obj, result);
328                return result;
329            }
330            catch(MetadataException e)
331            {
332                throw new PersistenceBrokerException(
333                        "Error while trying to autoincrement field " + f.getDeclaringClass() + "#" + f.getName(),
334                        e);
335            }
336            catch(SequenceManagerException e)
337            {
338                throw new PersistenceBrokerException("Could not get key value", e);
339            }
340        }
341    
342        /**
343         * Get the values of the fields for an obj
344         * Autoincrement values are automatically set.
345         * @param fields
346         * @param obj
347         * @throws PersistenceBrokerException
348         */
349        public ValueContainer[] getValuesForObject(FieldDescriptor[] fields, Object obj, boolean convertToSql, boolean assignAutoincrement) throws PersistenceBrokerException
350        {
351            ValueContainer[] result = new ValueContainer[fields.length];
352    
353            for(int i = 0; i < fields.length; i++)
354            {
355                FieldDescriptor fd = fields[i];
356                Object cv = fd.getPersistentField().get(obj);
357    
358                /*
359                handle autoincrement attributes if
360                - is a autoincrement field
361                - field represents a 'null' value, is nullified
362                and generate a new value
363                */
364                if(assignAutoincrement && fd.isAutoIncrement() && representsNull(fd, cv))
365                {
366                    /*
367                    setAutoIncrementValue returns a value that is
368                    properly typed for the java-world.  This value
369                    needs to be converted to it's corresponding
370                    sql type so that the entire result array contains
371                    objects that are properly typed for sql.
372                    */
373                    cv = setAutoIncrementValue(fd, obj);
374                }
375                if(convertToSql)
376                {
377                    // apply type and value conversion
378                    cv = fd.getFieldConversion().javaToSql(cv);
379                }
380                // create ValueContainer
381                result[i] = new ValueContainer(cv, fd.getJdbcType());
382            }
383            return result;
384        }
385    
386        public ValueContainer[] getValuesForObject(FieldDescriptor[] fields, Object obj, boolean convertToSql) throws PersistenceBrokerException
387        {
388            return getValuesForObject(fields, obj, convertToSql, false);
389        }
390    
391        /**
392         * Returns an array containing values for all non PK field READ/WRITE attributes of the object
393         * based on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
394         * <br/>
395         * NOTE: This method doesn't do any checks on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}
396         * the caller is reponsible to pass a valid descriptor.
397         *
398         * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} to extract the RW-fields
399         * @param obj The object with target fields to extract.
400         * @throws MetadataException if there is an erros accessing obj field values
401         */
402        public ValueContainer[] getNonKeyRwValues(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
403        {
404            return getValuesForObject(cld.getNonPkRwFields(), obj, true);
405        }
406    
407        /**
408         * Returns an array containing values for all READ/WRITE attributes of the object
409         * based on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
410         * <br/>
411         * NOTE: This method doesn't do any checks on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}
412         * the caller is reponsible to pass a valid descriptor.
413         *
414         * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} to extract the RW-fields
415         * @param obj The object with target fields to extract.
416         * @throws MetadataException if there is an erros accessing obj field values
417         */
418        public ValueContainer[] getAllRwValues(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
419        {
420            return getValuesForObject(cld.getAllRwFields(), obj, true);
421        }
422    
423        /**
424         * Extract an value array of the given {@link ValueContainer} array.
425         * @param containers
426         * @return An object array
427         */
428        public Object[] extractValueArray(ValueContainer[] containers)
429        {
430            Object[] result = new Object[containers.length];
431            for(int i = 0; i < containers.length; i++)
432            {
433                result[i] = containers[i].getValue();
434            }
435            return result;
436        }
437    
438        /**
439         * returns true if the primary key fields are valid for store, else false.
440         * PK fields are valid if each of them is either an OJB managed
441         * attribute (autoincrement or locking) or if it contains
442         * a valid non-null value
443         * @param fieldDescriptors the array of PK fielddescriptors
444         * @param pkValues the array of PK values
445         * @return boolean
446         */
447        public boolean assertValidPksForStore(FieldDescriptor[] fieldDescriptors, Object[] pkValues)
448        {
449            int fieldDescriptorSize = fieldDescriptors.length;
450            for(int i = 0; i < fieldDescriptorSize; i++)
451            {
452                FieldDescriptor fld = fieldDescriptors[i];
453                /**
454                 * a pk field is valid if it is either managed by OJB
455                 * (autoincrement or locking) or if it does contain a
456                 * valid non-null value.
457                 */
458                if(!(fld.isAutoIncrement()
459                        || fld.isLocking()
460                        || !representsNull(fld, pkValues[i])))
461                {
462                    return false;
463                }
464            }
465            return true;
466        }
467    
468        /**
469         * returns true if the primary key fields are valid for delete, else false.
470         * PK fields are valid if each of them contains a valid non-null value
471         * @param cld the ClassDescriptor
472         * @param obj the object
473         * @return boolean
474         */
475        public boolean assertValidPkForDelete(ClassDescriptor cld, Object obj)
476        {
477            if(!ProxyHelper.isProxy(obj))
478            {
479                FieldDescriptor fieldDescriptors[] = cld.getPkFields();
480                int fieldDescriptorSize = fieldDescriptors.length;
481                for(int i = 0; i < fieldDescriptorSize; i++)
482                {
483                    FieldDescriptor fd = fieldDescriptors[i];
484                    Object pkValue = fd.getPersistentField().get(obj);
485                    if (representsNull(fd, pkValue))
486                    {
487                        return false;
488                    }
489                }
490            }
491            return true;
492        }
493    
494        /**
495         * Build a Count-Query based on aQuery
496         * @param aQuery
497         * @return The count query
498         */
499        public Query getCountQuery(Query aQuery)
500        {
501            if(aQuery instanceof QueryBySQL)
502            {
503                return getQueryBySqlCount((QueryBySQL) aQuery);
504            }
505            else if(aQuery instanceof ReportQueryByCriteria)
506            {
507                return getReportQueryByCriteriaCount((ReportQueryByCriteria) aQuery);
508            }
509            else
510            {
511                return getQueryByCriteriaCount((QueryByCriteria) aQuery);
512            }
513        }
514    
515        /**
516         * Create a Count-Query for QueryBySQL
517         *
518         * @param aQuery
519         * @return The count query
520         */
521        private Query getQueryBySqlCount(QueryBySQL aQuery)
522        {
523            String countSql = aQuery.getSql();
524    
525            int fromPos = countSql.toUpperCase().indexOf(" FROM ");
526            if(fromPos >= 0)
527            {
528                countSql = "select count(*)" + countSql.substring(fromPos);
529            }
530    
531            int orderPos = countSql.toUpperCase().indexOf(" ORDER BY ");
532            if(orderPos >= 0)
533            {
534                countSql = countSql.substring(0, orderPos);
535            }
536    
537            return new QueryBySQL(aQuery.getSearchClass(), countSql);
538        }
539    
540        /**
541         * Create a Count-Query for QueryByCriteria
542         */
543        private Query getQueryByCriteriaCount(QueryByCriteria aQuery)
544        {
545            Class                 searchClass = aQuery.getSearchClass();
546            ReportQueryByCriteria countQuery  = null;
547            Criteria              countCrit   = null;
548            String[]              columns     = new String[1];
549    
550            // BRJ: copied Criteria without groupby, orderby, and prefetched relationships
551            if (aQuery.getCriteria() != null)
552            {
553                countCrit = aQuery.getCriteria().copy(false, false, false);
554            }
555    
556            if (aQuery.isDistinct())
557            {
558                // BRJ: Count distinct is dbms dependent
559                // hsql/sapdb: select count (distinct(person_id || project_id)) from person_project
560                // mysql: select count (distinct person_id,project_id) from person_project
561                // [tomdz]
562                // Some databases have no support for multi-column count distinct (e.g. Derby)
563                // Here we use a SELECT count(*) FROM (SELECT DISTINCT ...) instead 
564                //
565                // concatenation of pk-columns is a simple way to obtain a single column
566                // but concatenation is also dbms dependent:
567                //
568                // SELECT count(distinct concat(row1, row2, row3)) mysql
569                // SELECT count(distinct (row1 || row2 || row3)) ansi
570                // SELECT count(distinct (row1 + row2 + row3)) ms sql-server
571    
572                FieldDescriptor[] pkFields   = m_broker.getClassDescriptor(searchClass).getPkFields();
573                String[]          keyColumns = new String[pkFields.length];
574    
575                if (pkFields.length > 1)
576                {
577                    // TODO: Use ColumnName. This is a temporary solution because
578                    // we cannot yet resolve multiple columns in the same attribute.
579                    for (int idx = 0; idx < pkFields.length; idx++)
580                    {
581                        keyColumns[idx] = pkFields[idx].getColumnName();
582                    }
583                }
584                else
585                {
586                    for (int idx = 0; idx < pkFields.length; idx++)
587                    {
588                        keyColumns[idx] = pkFields[idx].getAttributeName();
589                    }
590                }
591                // [tomdz]
592                // TODO: Add support for databases that do not support COUNT DISTINCT over multiple columns
593    //            if (getPlatform().supportsMultiColumnCountDistinct())
594    //            {
595    //                columns[0] = "count(distinct " + getPlatform().concatenate(keyColumns) + ")";
596    //            }
597    //            else
598    //            {
599    //                columns = keyColumns;
600    //            }
601    
602                columns[0] = "count(distinct " + getPlatform().concatenate(keyColumns) + ")";
603            }
604            else
605            {
606                columns[0] = "count(*)";
607            }
608    
609            // BRJ: we have to preserve indirection table !
610            if (aQuery instanceof MtoNQuery)
611            {
612                MtoNQuery                 mnQuery       = (MtoNQuery)aQuery;
613                ReportQueryByMtoNCriteria mnReportQuery = new ReportQueryByMtoNCriteria(searchClass, columns, countCrit);
614    
615                mnReportQuery.setIndirectionTable(mnQuery.getIndirectionTable());
616                countQuery = mnReportQuery;
617            }
618            else
619            {
620                countQuery = new ReportQueryByCriteria(searchClass, columns, countCrit);
621            }
622    
623            // BRJ: we have to preserve outer-join-settings (by Andr� Markwalder)
624            for (Iterator outerJoinPath = aQuery.getOuterJoinPaths().iterator(); outerJoinPath.hasNext();)
625            {
626                String path = (String) outerJoinPath.next();
627    
628                if (aQuery.isPathOuterJoin(path))
629                {
630                    countQuery.setPathOuterJoin(path);
631                }
632            }
633    
634            //BRJ: add orderBy Columns asJoinAttributes
635            List orderBy = aQuery.getOrderBy();
636    
637            if ((orderBy != null) && !orderBy.isEmpty())
638            {
639                String[] joinAttributes = new String[orderBy.size()];
640    
641                for (int idx = 0; idx < orderBy.size(); idx++)
642                {
643                    joinAttributes[idx] = ((FieldHelper)orderBy.get(idx)).name;
644                }
645                countQuery.setJoinAttributes(joinAttributes);
646            }
647    
648            // [tomdz]
649            // TODO:
650            // For those databases that do not support COUNT DISTINCT over multiple columns
651            // we wrap the normal SELECT DISTINCT that we just created, into a SELECT count(*)
652            // For this however we need a report query that gets its data from a sub query instead
653            // of a table (target class)
654    //        if (aQuery.isDistinct() && !getPlatform().supportsMultiColumnCountDistinct())
655    //        {
656    //        }
657    
658            return countQuery;
659        }
660    
661        /**
662         * Create a Count-Query for ReportQueryByCriteria
663         */
664        private Query getReportQueryByCriteriaCount(ReportQueryByCriteria aQuery)
665        {
666            ReportQueryByCriteria countQuery = (ReportQueryByCriteria) getQueryByCriteriaCount(aQuery);
667    
668            // BRJ: keep the original columns to build the Join
669            countQuery.setJoinAttributes(aQuery.getAttributes());
670    
671            // BRJ: we have to preserve groupby information
672            Iterator iter = aQuery.getGroupBy().iterator();
673            while(iter.hasNext())
674            {
675                countQuery.addGroupBy((FieldHelper) iter.next());
676            }
677    
678            return countQuery;
679        }
680    
681        /**
682         * answer the platform
683         *
684         * @return the platform
685         */
686        private Platform getPlatform()
687        {
688            return m_broker.serviceSqlGenerator().getPlatform();
689        }
690    
691    
692        /*
693        NOTE: use weak key references to allow reclaiming
694        of no longer used ClassDescriptor instances
695        */
696        private Map sqlSelectMap = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD);
697        /**
698         * TODO: This method should be moved to {@link org.apache.ojb.broker.accesslayer.JdbcAccess}
699         * before 1.1 release.
700         *
701         * This method checks if the requested object can be
702         * found in database (without object materialization).
703         *
704         * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the
705         * object/{@link org.apache.ojb.broker.Identity} to check.
706         * @param obj The <em>object</em> to check.
707         * @param oid The associated {@link org.apache.ojb.broker.Identity}.
708         * {@link org.apache.ojb.broker.Identity} of the object
709         * @return Return <em>true</em> if the object is already persisted, <em>false</em> if the object is transient.
710         */
711        public boolean doesExist(ClassDescriptor cld, Identity oid, Object obj)
712        {
713            boolean result = false;
714            String sql = (String) sqlSelectMap.get(cld);
715            if(sql == null)
716            {
717                sql = new SqlExistStatement(cld, LoggerFactory.getDefaultLogger()).getStatement();
718                sqlSelectMap.put(cld, sql);
719            }
720            ValueContainer[] pkValues;
721            if(oid == null)
722            {
723                pkValues = getKeyValues(cld, obj, true);
724            }
725            else
726            {
727                pkValues = getKeyValues(cld, oid);
728            }
729            StatementManagerIF sm = m_broker.serviceStatementManager();
730            PreparedStatement stmt = null;
731            ResultSet rs = null;
732            try
733            {
734                stmt = sm.getPreparedStatement(cld, sql, false, 1, false);
735                sm.bindValues(stmt, pkValues, 1);
736                rs = stmt.executeQuery();
737                result = rs.next();
738            }
739            catch(SQLException e)
740            {
741                throw ExceptionHelper.generateException("[BrokerHelper#doesExist] Can't check if specified" +
742                        " object is already persisted", e, sql, cld, pkValues, null, obj);
743            }
744            finally
745            {
746                sm.closeResources(stmt, rs);
747            }
748    
749            return result;
750        }
751    
752        /**
753         * This method concatenate the main object with all reference
754         * objects (1:1, 1:n and m:n) by hand. This method is needed when
755         * in the reference metadata definitions the auto-xxx setting was disabled.
756         * More info see OJB doc.
757         */
758        public void link(Object obj, boolean insert)
759        {
760            linkOrUnlink(true, obj, insert);
761        }
762    
763        /**
764         * Unlink all references from this object.
765         * More info see OJB doc.
766         * @param obj Object with reference
767         */
768        public void unlink(Object obj)
769        {
770            linkOrUnlink(false, obj, false);
771        }
772    
773        private void linkOrUnlink(boolean doLink, Object obj, boolean insert)
774        {
775            ClassDescriptor cld = m_broker.getDescriptorRepository().getDescriptorFor(obj.getClass());
776    
777            if (cld.getObjectReferenceDescriptors().size() > 0)
778            {
779                // never returns null, thus we can direct call iterator
780                Iterator descriptors = cld.getObjectReferenceDescriptors().iterator();
781                while (descriptors.hasNext())
782                {
783                    ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) descriptors.next();
784                    linkOrUnlinkOneToOne(doLink, obj, ord, insert);
785                }
786            }
787            if (cld.getCollectionDescriptors().size() > 0)
788            {
789                // never returns null, thus we can direct call iterator
790                Iterator descriptors = cld.getCollectionDescriptors().iterator();
791                while (descriptors.hasNext())
792                {
793                    CollectionDescriptor cod = (CollectionDescriptor) descriptors.next();
794                    linkOrUnlinkXToMany(doLink, obj, cod, insert);
795                }
796            }
797        }
798    
799        /**
800         * This method concatenate the main object and the specified reference
801         * object (1:1 reference a referenced object, 1:n and m:n reference a
802         * collection of referenced objects) by hand. This method is needed when
803         * in the reference metadata definitions the auto-xxx setting was disabled.
804         * More info see OJB doc.
805         *
806         * @param obj Object with reference
807         * @param ord the ObjectReferenceDescriptor of the reference
808         * @param insert flag signals insert operation
809         */
810        public void link(Object obj, ObjectReferenceDescriptor ord, boolean insert)
811        {
812           linkOrUnlink(true, obj, ord, insert);
813        }
814    
815        /**
816         * This method concatenate the main object and the specified reference
817         * object (1:1 reference a referenced object, 1:n and m:n reference a
818         * collection of referenced objects) by hand. This method is needed when
819         * in the reference metadata definitions the auto-xxx setting was disabled.
820         * More info see OJB doc.
821         *
822         * @param obj Object with reference
823         * @param attributeName field name of the reference
824         * @param insert flag signals insert operation
825         * @return true if the specified reference was found and linking was successful
826         */
827        public boolean link(Object obj, String attributeName, boolean insert)
828        {
829           return linkOrUnlink(true, obj, attributeName, insert);
830        }
831    
832        /**
833         * This method concatenate the main object and the specified reference
834         * object (1:1 reference a referenced object, 1:n and m:n reference a
835         * collection of referenced objects) by hand. This method is needed when
836         * in the reference metadata definitions the auto-xxx setting was disabled.
837         * More info see OJB doc.
838         *
839         * @param obj Object with reference
840         * @param attributeName field name of the reference
841         * @param reference The referenced object
842         * @param insert flag signals insert operation
843         * @return true if the specified reference was found and linking was successful
844         */
845        public boolean link(Object obj, String attributeName, Object reference, boolean insert)
846        {
847            ClassDescriptor cld = m_broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(obj));
848            ObjectReferenceDescriptor ord;
849            boolean match = false;
850            // first look for reference then for collection
851            ord = cld.getObjectReferenceDescriptorByName(attributeName);
852            if (ord != null)
853            {
854                linkOrUnlinkOneToOne(true, obj, ord, insert);
855                match = true;
856            }
857            else
858            {
859                CollectionDescriptor cod = cld.getCollectionDescriptorByName(attributeName);
860                if (cod != null)
861                {
862                    linkOrUnlinkXToMany(true, obj, cod, insert);
863                    match = true;
864                }
865            }
866            return match;
867        }
868    
869        /**
870         * Unlink the specified reference object.
871         * More info see OJB doc.
872         * @param source The source object with the specified reference field.
873         * @param attributeName The field name of the reference to unlink.
874         * @param target The referenced object to unlink.
875         */
876        public boolean unlink(Object source, String attributeName, Object target)
877        {
878            return linkOrUnlink(false, source, attributeName, false);
879        }
880    
881        /**
882         * Unlink all referenced objects of the specified field.
883         * More info see OJB doc.
884         * @param source The source object with the specified reference.
885         * @param attributeName The field name of the reference to unlink.
886         */
887        public boolean unlink(Object source, String attributeName)
888        {
889            return linkOrUnlink(false, source, attributeName, false);
890        }
891    
892        /**
893         * Unlink the specified reference from this object.
894         * More info see OJB doc.
895         *
896         * @param obj Object with reference
897         * @param ord the ObjectReferenceDescriptor of the reference
898         * @param insert flag signals insert operation
899         */
900        public void unlink(Object obj, ObjectReferenceDescriptor ord, boolean insert)
901        {
902           linkOrUnlink(false, obj, ord, insert);
903        }
904    
905        private boolean linkOrUnlink(boolean doLink, Object obj, String attributeName, boolean insert)
906        {
907            boolean match = false;
908            ClassDescriptor cld = m_broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(obj));
909            ObjectReferenceDescriptor ord;
910    
911            // first look for reference then for collection
912            ord = cld.getObjectReferenceDescriptorByName(attributeName);
913            if (ord != null)
914            {
915                linkOrUnlinkOneToOne(doLink, obj, ord, insert);
916                match = true;
917            }
918            else
919            {
920                CollectionDescriptor cod = cld.getCollectionDescriptorByName(attributeName);
921                if (cod != null)
922                {
923                    linkOrUnlinkXToMany(doLink, obj, cod, insert);
924                    match = true;
925                }
926            }
927    
928            return match;
929        }
930    
931        private void linkOrUnlink(boolean doLink, Object obj, ObjectReferenceDescriptor ord, boolean insert)
932        {
933            if (ord instanceof CollectionDescriptor)
934            {
935                linkOrUnlinkXToMany(doLink, obj, (CollectionDescriptor) ord, insert);
936            }
937            else
938            {
939                linkOrUnlinkOneToOne(doLink, obj, ord, insert);
940            }
941        }
942    
943        private void linkOrUnlinkXToMany(boolean doLink, Object obj, CollectionDescriptor cod, boolean insert)
944        {
945            if (doLink)
946            {
947                if (cod.isMtoNRelation())
948                {
949                    m_broker.linkMtoN(obj, cod, insert);
950                }
951                else
952                {
953                    m_broker.linkOneToMany(obj, cod, insert);
954                }
955            }
956            else
957            {
958                m_broker.unlinkXtoN(obj, cod);
959            }
960        }
961    
962        private void linkOrUnlinkOneToOne(boolean doLink, Object obj, ObjectReferenceDescriptor ord, boolean insert)
963        {
964            /*
965            arminw: we need the class-descriptor where the reference is declared, thus we ask the
966            reference-descriptor for this, instead of using the class-descriptor of the specified
967            object. If the reference was declared within an interface (should never happen) we
968            only can use the descriptor of the real class.
969            */
970            ClassDescriptor cld = ord.getClassDescriptor();
971            if(cld.isInterface())
972            {
973                cld = m_broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(obj));
974            }
975    
976            if (doLink)
977            {
978                m_broker.linkOneToOne(obj, cld, ord, insert);
979            }
980            else
981            {
982                m_broker.unlinkFK(obj, cld, ord);
983                // in 1:1 relation we have to set relation to null
984                ord.getPersistentField().set(obj, null);
985            }
986        }
987    
988        /**
989         * Unlink a bunch of 1:n or m:n objects.
990         *
991         * @param source The source object with reference.
992         * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
993         * @param referencesToUnlink List of referenced objects to unlink.
994         */
995        public void unlink(Object source, CollectionDescriptor cds, List referencesToUnlink)
996        {
997            for(int i = 0; i < referencesToUnlink.size(); i++)
998            {
999                unlink(source, cds, referencesToUnlink.get(i));
1000            }
1001        }
1002    
1003        /**
1004         * Unlink a single 1:n or m:n object.
1005         *
1006         * @param source The source object with reference.
1007         * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
1008         * @param referenceToUnlink The referenced object to link.
1009         */
1010        public void unlink(Object source, CollectionDescriptor cds, Object referenceToUnlink)
1011        {
1012            if(cds.isMtoNRelation())
1013            {
1014                m_broker.deleteMtoNImplementor(new MtoNImplementor(cds, source, referenceToUnlink));
1015            }
1016            else
1017            {
1018                ClassDescriptor cld = m_broker.getClassDescriptor(referenceToUnlink.getClass());
1019                m_broker.unlinkFK(referenceToUnlink, cld, cds);
1020            }
1021        }
1022    
1023        /**
1024         * Link a bunch of 1:n or m:n objects.
1025         *
1026         * @param source The source object with reference.
1027         * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
1028         * @param referencesToLink List of referenced objects to link.
1029         */
1030        public void link(Object source, CollectionDescriptor cds, List referencesToLink)
1031        {
1032            for(int i = 0; i < referencesToLink.size(); i++)
1033            {
1034                link(source, cds, referencesToLink.get(i));
1035            }
1036        }
1037    
1038        /**
1039         * Link a single 1:n or m:n object.
1040         *
1041         * @param source The source object with the declared reference.
1042         * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation declared in source object.
1043         * @param referenceToLink The referenced object to link.
1044         */
1045        public void link(Object source, CollectionDescriptor cds, Object referenceToLink)
1046        {
1047            if(cds.isMtoNRelation())
1048            {
1049                m_broker.addMtoNImplementor(new MtoNImplementor(cds, source, referenceToLink));
1050            }
1051            else
1052            {
1053                ClassDescriptor cld = m_broker.getClassDescriptor(referenceToLink.getClass());
1054                m_broker.link(referenceToLink, cld, cds, source, false);
1055            }
1056        }
1057    
1058        /**
1059         * Returns an Iterator instance for {@link java.util.Collection}, object Array or
1060         * {@link org.apache.ojb.broker.ManageableCollection} instances.
1061         *
1062         * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
1063         * Array or {@link org.apache.ojb.broker.ManageableCollection}.
1064         * @return Iterator able to handle given collection object
1065         */
1066        public static Iterator getCollectionIterator(Object collectionOrArray)
1067        {
1068            Iterator colIterator;
1069            if (collectionOrArray instanceof ManageableCollection)
1070            {
1071                colIterator = ((ManageableCollection) collectionOrArray).ojbIterator();
1072            }
1073            else if (collectionOrArray instanceof Collection)
1074            {
1075                colIterator = ((Collection) collectionOrArray).iterator();
1076            }
1077            else if (collectionOrArray.getClass().isArray())
1078            {
1079                colIterator = new ArrayIterator(collectionOrArray);
1080            }
1081            else
1082            {
1083                throw new OJBRuntimeException( "Given object collection of type '"
1084                        + (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
1085                    + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
1086            }
1087            return colIterator;
1088        }
1089    
1090        /**
1091         * Returns an object array for {@link java.util.Collection}, array or
1092         * {@link org.apache.ojb.broker.ManageableCollection} instances.
1093         *
1094         * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
1095         * Array or {@link org.apache.ojb.broker.ManageableCollection}.
1096         * @return Object array able to handle given collection or array object
1097         */
1098        public static Object[] getCollectionArray(Object collectionOrArray)
1099        {
1100            Object[] result;
1101            if (collectionOrArray instanceof Collection)
1102            {
1103                result = ((Collection) collectionOrArray).toArray();
1104            }
1105            else if (collectionOrArray instanceof ManageableCollection)
1106            {
1107                Collection newCol = new ArrayList();
1108                CollectionUtils.addAll(newCol, ((ManageableCollection) collectionOrArray).ojbIterator());
1109                result = newCol.toArray();
1110            }
1111            else if (collectionOrArray.getClass().isArray())
1112            {
1113                result = (Object[]) collectionOrArray;
1114            }
1115            else
1116            {
1117                throw new OJBRuntimeException( "Given object collection of type '"
1118                        + (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
1119                    + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
1120            }
1121            return result;
1122        }
1123    
1124        /**
1125         * Returns <em>true</em> if one or more anonymous FK fields are used.
1126         * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the main object.
1127         * @param rds The {@link org.apache.ojb.broker.metadata.ObjectReferenceDescriptor} of the referenced object.
1128         * @return <em>true</em> if one or more anonymous FK fields are used for specified reference.
1129         */
1130        public static boolean hasAnonymousKeyReference(ClassDescriptor cld, ObjectReferenceDescriptor rds)
1131        {
1132            boolean result = false;
1133            FieldDescriptor[] fkFields = rds.getForeignKeyFieldDescriptors(cld);
1134            for(int i = 0; i < fkFields.length; i++)
1135            {
1136                FieldDescriptor fkField = fkFields[i];
1137                if(fkField.isAnonymous())
1138                {
1139                    result = true;
1140                    break;
1141                }
1142            }
1143            return result;
1144        }
1145    
1146    //    /**
1147    //     * Use this method to extract the {@link org.apache.ojb.broker.metadata.ClassDescriptor} where
1148    //     * the {@link org.apache.ojb.broker.metadata.ObjectReferenceDescriptor reference} is declared.
1149    //     * It's possible that the reference is declared in a super-class.
1150    //     * @param broker
1151    //     * @param reference
1152    //     * @param source
1153    //     * @return
1154    //     */
1155    //    public static ClassDescriptor extractDescriptorForReference(PersistenceBroker broker, ObjectReferenceDescriptor reference, Object source)
1156    //    {
1157    //        /*
1158    //        arminw: we need the class-descriptor where the reference is declared, thus we ask the
1159    //        reference-descriptor for this, instead of using the class-descriptor of the specified
1160    //        object. If the reference was declared within an interface (should never happen) we
1161    //        only can use the descriptor of the real class.
1162    //        */
1163    //        ClassDescriptor cld = reference.getClassDescriptor();
1164    //        if(cld.isInterface())
1165    //        {
1166    //            cld = broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(source));
1167    //        }
1168    //        return cld;
1169    //    }
1170    
1171    //    /**
1172    //     * Returns a {@link java.util.List} instance of the specified object in method argument,
1173    //     * in which the argument must be of type {@link java.util.Collection}, array or
1174    //     * {@link org.apache.ojb.broker.ManageableCollection}.
1175    //     *
1176    //     * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
1177    //     * Array or {@link org.apache.ojb.broker.ManageableCollection}.
1178    //     * @return Object array able to handle given collection or array object
1179    //     */
1180    //    public static List getCollectionList(Object collectionOrArray)
1181    //    {
1182    //        List result = null;
1183    //        if (collectionOrArray instanceof Collection)
1184    //        {
1185    //            result = ((Collection) collectionOrArray).toArray();
1186    //        }
1187    //        else if (collectionOrArray instanceof ManageableCollection)
1188    //        {
1189    //            Collection newCol = new ArrayList();
1190    //            CollectionUtils.addAll(newCol, ((ManageableCollection) collectionOrArray).ojbIterator());
1191    //            result = newCol.toArray();
1192    //        }
1193    //        else if (collectionOrArray.getClass().isArray())
1194    //        {
1195    //            result = (Object[]) collectionOrArray;
1196    //        }
1197    //        else
1198    //        {
1199    //            throw new OJBRuntimeException( "Given object collection of type '"
1200    //                    + (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
1201    //                + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
1202    //        }
1203    //        return result;
1204    //    }
1205    }