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 }