View Javadoc

1   package org.apache.ojb.broker.util;
2   
3   /* Copyright 2002-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.sql.PreparedStatement;
19  import java.sql.ResultSet;
20  import java.sql.SQLException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.StringTokenizer;
27  
28  import org.apache.commons.collections.CollectionUtils;
29  import org.apache.commons.collections.iterators.ArrayIterator;
30  import org.apache.commons.collections.map.ReferenceIdentityMap;
31  import org.apache.ojb.broker.Identity;
32  import org.apache.ojb.broker.ManageableCollection;
33  import org.apache.ojb.broker.MtoNImplementor;
34  import org.apache.ojb.broker.OJBRuntimeException;
35  import org.apache.ojb.broker.PBKey;
36  import org.apache.ojb.broker.PersistenceBrokerException;
37  import org.apache.ojb.broker.accesslayer.StatementManagerIF;
38  import org.apache.ojb.broker.accesslayer.sql.SqlExistStatement;
39  import org.apache.ojb.broker.core.PersistenceBrokerImpl;
40  import org.apache.ojb.broker.core.ValueContainer;
41  import org.apache.ojb.broker.core.proxy.IndirectionHandler;
42  import org.apache.ojb.broker.core.proxy.ProxyHelper;
43  import org.apache.ojb.broker.metadata.ClassDescriptor;
44  import org.apache.ojb.broker.metadata.CollectionDescriptor;
45  import org.apache.ojb.broker.metadata.FieldDescriptor;
46  import org.apache.ojb.broker.metadata.FieldHelper;
47  import org.apache.ojb.broker.metadata.MetadataException;
48  import org.apache.ojb.broker.metadata.MetadataManager;
49  import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
50  import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
51  import org.apache.ojb.broker.platforms.Platform;
52  import org.apache.ojb.broker.query.Criteria;
53  import org.apache.ojb.broker.query.MtoNQuery;
54  import org.apache.ojb.broker.query.Query;
55  import org.apache.ojb.broker.query.QueryByCriteria;
56  import org.apache.ojb.broker.query.QueryBySQL;
57  import org.apache.ojb.broker.query.ReportQueryByCriteria;
58  import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
59  import org.apache.ojb.broker.util.logging.LoggerFactory;
60  import org.apache.ojb.broker.util.sequence.SequenceManagerException;
61  
62  /**
63   * This class contains helper methods primarily used by the {@link org.apache.ojb.broker.PersistenceBroker}
64   * implementation (e.g. contains methods to assign the the values of 'autoincrement' fields).
65   * <br/>
66   * Furthermore it was used to introduce new features related to {@link org.apache.ojb.broker.PersistenceBroker} - these
67   * new features and services (if they stand the test of time) will be moved to separate services in future.
68   *
69   * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
70   * @version $Id: BrokerHelper.java,v 1.1 2007-08-24 22:17:36 ewestfal Exp $
71   */
72  public class BrokerHelper
73  {
74      public static final String REPOSITORY_NAME_SEPARATOR = "#";
75      private PersistenceBrokerImpl m_broker;
76  
77      public BrokerHelper(PersistenceBrokerImpl broker)
78      {
79          this.m_broker = broker;
80      }
81  
82      /**
83       * splits up the name string and extract db url,
84       * user name and password and build a new PBKey
85       * instance - the token '#' is used to separate
86       * the substrings.
87       * @throws PersistenceBrokerException if given name was <code>null</code>
88       */
89      public static PBKey extractAllTokens(String name)
90      {
91          if(name == null)
92          {
93              throw new PersistenceBrokerException("Could not extract PBKey, given argument is 'null'");
94          }
95          String user = null;
96          String passwd = null;
97          StringTokenizer tok = new StringTokenizer(name, REPOSITORY_NAME_SEPARATOR);
98          String dbName = tok.nextToken();
99          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 }