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 }