0001: package org.apache.ojb.broker.util;
0002:
0003: /* Copyright 2002-2005 The Apache Software Foundation
0004: *
0005: * Licensed under the Apache License, Version 2.0 (the "License");
0006: * you may not use this file except in compliance with the License.
0007: * You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: import java.sql.PreparedStatement;
0019: import java.sql.ResultSet;
0020: import java.sql.SQLException;
0021: import java.util.ArrayList;
0022: import java.util.Collection;
0023: import java.util.Iterator;
0024: import java.util.List;
0025: import java.util.Map;
0026: import java.util.StringTokenizer;
0027:
0028: import org.apache.commons.collections.CollectionUtils;
0029: import org.apache.commons.collections.iterators.ArrayIterator;
0030: import org.apache.commons.collections.map.ReferenceIdentityMap;
0031: import org.apache.ojb.broker.Identity;
0032: import org.apache.ojb.broker.ManageableCollection;
0033: import org.apache.ojb.broker.MtoNImplementor;
0034: import org.apache.ojb.broker.OJBRuntimeException;
0035: import org.apache.ojb.broker.PBKey;
0036: import org.apache.ojb.broker.PersistenceBrokerException;
0037: import org.apache.ojb.broker.accesslayer.StatementManagerIF;
0038: import org.apache.ojb.broker.accesslayer.sql.SqlExistStatement;
0039: import org.apache.ojb.broker.core.PersistenceBrokerImpl;
0040: import org.apache.ojb.broker.core.ValueContainer;
0041: import org.apache.ojb.broker.core.proxy.IndirectionHandler;
0042: import org.apache.ojb.broker.core.proxy.ProxyHelper;
0043: import org.apache.ojb.broker.metadata.ClassDescriptor;
0044: import org.apache.ojb.broker.metadata.CollectionDescriptor;
0045: import org.apache.ojb.broker.metadata.FieldDescriptor;
0046: import org.apache.ojb.broker.metadata.FieldHelper;
0047: import org.apache.ojb.broker.metadata.MetadataException;
0048: import org.apache.ojb.broker.metadata.MetadataManager;
0049: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
0050: import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
0051: import org.apache.ojb.broker.platforms.Platform;
0052: import org.apache.ojb.broker.query.Criteria;
0053: import org.apache.ojb.broker.query.MtoNQuery;
0054: import org.apache.ojb.broker.query.Query;
0055: import org.apache.ojb.broker.query.QueryByCriteria;
0056: import org.apache.ojb.broker.query.QueryBySQL;
0057: import org.apache.ojb.broker.query.ReportQueryByCriteria;
0058: import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
0059: import org.apache.ojb.broker.util.logging.LoggerFactory;
0060: import org.apache.ojb.broker.util.sequence.SequenceManagerException;
0061:
0062: /**
0063: * This class contains helper methods primarily used by the {@link org.apache.ojb.broker.PersistenceBroker}
0064: * implementation (e.g. contains methods to assign the the values of 'autoincrement' fields).
0065: * <br/>
0066: * Furthermore it was used to introduce new features related to {@link org.apache.ojb.broker.PersistenceBroker} - these
0067: * new features and services (if they stand the test of time) will be moved to separate services in future.
0068: *
0069: * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
0070: * @version $Id: BrokerHelper.java,v 1.57.2.23 2005/12/21 22:27:47 tomdz Exp $
0071: */
0072: public class BrokerHelper {
0073: public static final String REPOSITORY_NAME_SEPARATOR = "#";
0074: private PersistenceBrokerImpl m_broker;
0075:
0076: public BrokerHelper(PersistenceBrokerImpl broker) {
0077: this .m_broker = broker;
0078: }
0079:
0080: /**
0081: * splits up the name string and extract db url,
0082: * user name and password and build a new PBKey
0083: * instance - the token '#' is used to separate
0084: * the substrings.
0085: * @throws PersistenceBrokerException if given name was <code>null</code>
0086: */
0087: public static PBKey extractAllTokens(String name) {
0088: if (name == null) {
0089: throw new PersistenceBrokerException(
0090: "Could not extract PBKey, given argument is 'null'");
0091: }
0092: String user = null;
0093: String passwd = null;
0094: StringTokenizer tok = new StringTokenizer(name,
0095: REPOSITORY_NAME_SEPARATOR);
0096: String dbName = tok.nextToken();
0097: if (tok.hasMoreTokens()) {
0098: user = tok.nextToken();
0099: if (user != null && user.trim().equals("")) {
0100: user = null;
0101: }
0102: }
0103: if (tok.hasMoreTokens()) {
0104: if (user != null)
0105: passwd = tok.nextToken();
0106: }
0107: if (user != null && passwd == null) {
0108: passwd = "";
0109: }
0110: return new PBKey(dbName, user, passwd);
0111: }
0112:
0113: /**
0114: * Check if the user of the given PBKey was <code>null</code>, if so we try to
0115: * get user/password from the jdbc-connection-descriptor matching the given
0116: * PBKey.getAlias().
0117: */
0118: public static PBKey crossCheckPBKey(PBKey key) {
0119: if (key.getUser() == null) {
0120: PBKey defKey = MetadataManager.getInstance()
0121: .connectionRepository()
0122: .getStandardPBKeyForJcdAlias(key.getAlias());
0123: if (defKey != null) {
0124: return defKey;
0125: }
0126: }
0127: return key;
0128: }
0129:
0130: /**
0131: * Answer the real ClassDescriptor for anObj
0132: * ie. aCld may be an Interface of anObj, so the cld for anObj is returned
0133: */
0134: private ClassDescriptor getRealClassDescriptor(
0135: ClassDescriptor aCld, Object anObj) {
0136: ClassDescriptor result;
0137:
0138: if (aCld.getClassOfObject() == ProxyHelper.getRealClass(anObj)) {
0139: result = aCld;
0140: } else {
0141: result = aCld.getRepository().getDescriptorFor(
0142: anObj.getClass());
0143: }
0144:
0145: return result;
0146: }
0147:
0148: /**
0149: * Returns an Array with an Objects PK VALUES if convertToSql is true, any
0150: * associated java-to-sql conversions are applied. If the Object is a Proxy
0151: * or a VirtualProxy NO conversion is necessary.
0152: *
0153: * @param objectOrProxy
0154: * @param convertToSql
0155: * @return Object[]
0156: * @throws PersistenceBrokerException
0157: */
0158: public ValueContainer[] getKeyValues(ClassDescriptor cld,
0159: Object objectOrProxy, boolean convertToSql)
0160: throws PersistenceBrokerException {
0161: IndirectionHandler handler = ProxyHelper
0162: .getIndirectionHandler(objectOrProxy);
0163:
0164: if (handler != null) {
0165: return getKeyValues(cld, handler.getIdentity(),
0166: convertToSql); //BRJ: convert Identity
0167: } else {
0168: ClassDescriptor realCld = getRealClassDescriptor(cld,
0169: objectOrProxy);
0170: return getValuesForObject(realCld.getPkFields(),
0171: objectOrProxy, convertToSql);
0172: }
0173: }
0174:
0175: /**
0176: * Return primary key values of given Identity object.
0177: *
0178: * @param cld
0179: * @param oid
0180: * @return Object[]
0181: * @throws PersistenceBrokerException
0182: */
0183: public ValueContainer[] getKeyValues(ClassDescriptor cld,
0184: Identity oid) throws PersistenceBrokerException {
0185: return getKeyValues(cld, oid, true);
0186: }
0187:
0188: /**
0189: * Return key Values of an Identity
0190: * @param cld
0191: * @param oid
0192: * @param convertToSql
0193: * @return Object[]
0194: * @throws PersistenceBrokerException
0195: */
0196: public ValueContainer[] getKeyValues(ClassDescriptor cld,
0197: Identity oid, boolean convertToSql)
0198: throws PersistenceBrokerException {
0199: FieldDescriptor[] pkFields = cld.getPkFields();
0200: ValueContainer[] result = new ValueContainer[pkFields.length];
0201: Object[] pkValues = oid.getPrimaryKeyValues();
0202:
0203: try {
0204: for (int i = 0; i < result.length; i++) {
0205: FieldDescriptor fd = pkFields[i];
0206: Object cv = pkValues[i];
0207: if (convertToSql) {
0208: // BRJ : apply type and value mapping
0209: cv = fd.getFieldConversion().javaToSql(cv);
0210: }
0211: result[i] = new ValueContainer(cv, fd.getJdbcType());
0212: }
0213: } catch (Exception e) {
0214: throw new PersistenceBrokerException(
0215: "Can't generate primary key values for given Identity "
0216: + oid, e);
0217: }
0218: return result;
0219: }
0220:
0221: /**
0222: * returns an Array with an Objects PK VALUES, with any java-to-sql
0223: * FieldConversion applied. If the Object is a Proxy or a VirtualProxy NO
0224: * conversion is necessary.
0225: *
0226: * @param objectOrProxy
0227: * @return Object[]
0228: * @throws PersistenceBrokerException
0229: */
0230: public ValueContainer[] getKeyValues(ClassDescriptor cld,
0231: Object objectOrProxy) throws PersistenceBrokerException {
0232: return getKeyValues(cld, objectOrProxy, true);
0233: }
0234:
0235: /**
0236: * Decide if the given object value represents 'null'.<br/>
0237: *
0238: * - If given value is 'null' itself, true will be returned<br/>
0239: *
0240: * - If given value is instance of Number with value 0 and the field-descriptor
0241: * represents a primitive field, true will be returned<br/>
0242: *
0243: * - If given value is instance of String with length 0 and the field-descriptor
0244: * is a primary key, true will be returned<br/>
0245: */
0246: public boolean representsNull(FieldDescriptor fld, Object aValue) {
0247: if (aValue == null)
0248: return true;
0249:
0250: boolean result = false;
0251: if (((aValue instanceof Number) && (((Number) aValue)
0252: .longValue() == 0))) {
0253: Class type = fld.getPersistentField().getType();
0254: /*
0255: AnonymousPersistentFields will *always* have a null type according to the
0256: javadoc comments in AnonymousPersistentField.getType() and never represents
0257: a primitve java field with value 0, thus we return always 'false' in this case.
0258: (If the value object is null, the first check above return true)
0259: */
0260: if (type != null) {
0261: result = type.isPrimitive();
0262: }
0263: }
0264: // TODO: Do we need this check?? String could be nullified, why should we assume
0265: // it's 'null' on empty string?
0266: else if ((aValue instanceof String)
0267: && (((String) aValue).length() == 0)) {
0268: result = fld.isPrimaryKey();
0269: }
0270: return result;
0271: }
0272:
0273: /**
0274: * Detect if the given object has a PK field represents a 'null' value.
0275: */
0276: public boolean hasNullPKField(ClassDescriptor cld, Object obj) {
0277: FieldDescriptor[] fields = cld.getPkFields();
0278: boolean hasNull = false;
0279: // an unmaterialized proxy object can never have nullified PK's
0280: IndirectionHandler handler = ProxyHelper
0281: .getIndirectionHandler(obj);
0282: if (handler == null || handler.alreadyMaterialized()) {
0283: if (handler != null)
0284: obj = handler.getRealSubject();
0285: FieldDescriptor fld;
0286: for (int i = 0; i < fields.length; i++) {
0287: fld = fields[i];
0288: hasNull = representsNull(fld, fld.getPersistentField()
0289: .get(obj));
0290: if (hasNull)
0291: break;
0292: }
0293: }
0294: return hasNull;
0295: }
0296:
0297: /**
0298: * Set an autoincremented value in given object field that has already
0299: * had a field conversion run on it, if an value for the given field is
0300: * already set, it will be overridden - no further checks are done.
0301: * <p>
0302: * The data type of the value that is returned by this method is
0303: * compatible with the java-world. The return value has <b>NOT</b>
0304: * been run through a field conversion and converted to a corresponding
0305: * sql-type.
0306: *
0307: * @return the autoincremented value set on given object
0308: * @throws PersistenceBrokerException if there is an erros accessing obj field values
0309: */
0310: private Object setAutoIncrementValue(FieldDescriptor fd, Object obj) {
0311: PersistentField f = fd.getPersistentField();
0312: try {
0313: // lookup SeqMan for a value matching db column an
0314: Object result = m_broker.serviceSequenceManager()
0315: .getUniqueValue(fd);
0316: // reflect autoincrement value back into object
0317: f.set(obj, result);
0318: return result;
0319: } catch (MetadataException e) {
0320: throw new PersistenceBrokerException(
0321: "Error while trying to autoincrement field "
0322: + f.getDeclaringClass() + "#" + f.getName(),
0323: e);
0324: } catch (SequenceManagerException e) {
0325: throw new PersistenceBrokerException(
0326: "Could not get key value", e);
0327: }
0328: }
0329:
0330: /**
0331: * Get the values of the fields for an obj
0332: * Autoincrement values are automatically set.
0333: * @param fields
0334: * @param obj
0335: * @throws PersistenceBrokerException
0336: */
0337: public ValueContainer[] getValuesForObject(
0338: FieldDescriptor[] fields, Object obj, boolean convertToSql,
0339: boolean assignAutoincrement)
0340: throws PersistenceBrokerException {
0341: ValueContainer[] result = new ValueContainer[fields.length];
0342:
0343: for (int i = 0; i < fields.length; i++) {
0344: FieldDescriptor fd = fields[i];
0345: Object cv = fd.getPersistentField().get(obj);
0346:
0347: /*
0348: handle autoincrement attributes if
0349: - is a autoincrement field
0350: - field represents a 'null' value, is nullified
0351: and generate a new value
0352: */
0353: if (assignAutoincrement && fd.isAutoIncrement()
0354: && representsNull(fd, cv)) {
0355: /*
0356: setAutoIncrementValue returns a value that is
0357: properly typed for the java-world. This value
0358: needs to be converted to it's corresponding
0359: sql type so that the entire result array contains
0360: objects that are properly typed for sql.
0361: */
0362: cv = setAutoIncrementValue(fd, obj);
0363: }
0364: if (convertToSql) {
0365: // apply type and value conversion
0366: cv = fd.getFieldConversion().javaToSql(cv);
0367: }
0368: // create ValueContainer
0369: result[i] = new ValueContainer(cv, fd.getJdbcType());
0370: }
0371: return result;
0372: }
0373:
0374: public ValueContainer[] getValuesForObject(
0375: FieldDescriptor[] fields, Object obj, boolean convertToSql)
0376: throws PersistenceBrokerException {
0377: return getValuesForObject(fields, obj, convertToSql, false);
0378: }
0379:
0380: /**
0381: * Returns an array containing values for all non PK field READ/WRITE attributes of the object
0382: * based on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
0383: * <br/>
0384: * NOTE: This method doesn't do any checks on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}
0385: * the caller is reponsible to pass a valid descriptor.
0386: *
0387: * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} to extract the RW-fields
0388: * @param obj The object with target fields to extract.
0389: * @throws MetadataException if there is an erros accessing obj field values
0390: */
0391: public ValueContainer[] getNonKeyRwValues(ClassDescriptor cld,
0392: Object obj) throws PersistenceBrokerException {
0393: return getValuesForObject(cld.getNonPkRwFields(), obj, true);
0394: }
0395:
0396: /**
0397: * Returns an array containing values for all READ/WRITE attributes of the object
0398: * based on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}.
0399: * <br/>
0400: * NOTE: This method doesn't do any checks on the specified {@link org.apache.ojb.broker.metadata.ClassDescriptor}
0401: * the caller is reponsible to pass a valid descriptor.
0402: *
0403: * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} to extract the RW-fields
0404: * @param obj The object with target fields to extract.
0405: * @throws MetadataException if there is an erros accessing obj field values
0406: */
0407: public ValueContainer[] getAllRwValues(ClassDescriptor cld,
0408: Object obj) throws PersistenceBrokerException {
0409: return getValuesForObject(cld.getAllRwFields(), obj, true);
0410: }
0411:
0412: /**
0413: * Extract an value array of the given {@link ValueContainer} array.
0414: * @param containers
0415: * @return An object array
0416: */
0417: public Object[] extractValueArray(ValueContainer[] containers) {
0418: Object[] result = new Object[containers.length];
0419: for (int i = 0; i < containers.length; i++) {
0420: result[i] = containers[i].getValue();
0421: }
0422: return result;
0423: }
0424:
0425: /**
0426: * returns true if the primary key fields are valid for store, else false.
0427: * PK fields are valid if each of them is either an OJB managed
0428: * attribute (autoincrement or locking) or if it contains
0429: * a valid non-null value
0430: * @param fieldDescriptors the array of PK fielddescriptors
0431: * @param pkValues the array of PK values
0432: * @return boolean
0433: */
0434: public boolean assertValidPksForStore(
0435: FieldDescriptor[] fieldDescriptors, Object[] pkValues) {
0436: int fieldDescriptorSize = fieldDescriptors.length;
0437: for (int i = 0; i < fieldDescriptorSize; i++) {
0438: FieldDescriptor fld = fieldDescriptors[i];
0439: /**
0440: * a pk field is valid if it is either managed by OJB
0441: * (autoincrement or locking) or if it does contain a
0442: * valid non-null value.
0443: */
0444: if (!(fld.isAutoIncrement() || fld.isLocking() || !representsNull(
0445: fld, pkValues[i]))) {
0446: return false;
0447: }
0448: }
0449: return true;
0450: }
0451:
0452: /**
0453: * returns true if the primary key fields are valid for delete, else false.
0454: * PK fields are valid if each of them contains a valid non-null value
0455: * @param cld the ClassDescriptor
0456: * @param obj the object
0457: * @return boolean
0458: */
0459: public boolean assertValidPkForDelete(ClassDescriptor cld,
0460: Object obj) {
0461: if (!ProxyHelper.isProxy(obj)) {
0462: FieldDescriptor fieldDescriptors[] = cld.getPkFields();
0463: int fieldDescriptorSize = fieldDescriptors.length;
0464: for (int i = 0; i < fieldDescriptorSize; i++) {
0465: FieldDescriptor fd = fieldDescriptors[i];
0466: Object pkValue = fd.getPersistentField().get(obj);
0467: if (representsNull(fd, pkValue)) {
0468: return false;
0469: }
0470: }
0471: }
0472: return true;
0473: }
0474:
0475: /**
0476: * Build a Count-Query based on aQuery
0477: * @param aQuery
0478: * @return The count query
0479: */
0480: public Query getCountQuery(Query aQuery) {
0481: if (aQuery instanceof QueryBySQL) {
0482: return getQueryBySqlCount((QueryBySQL) aQuery);
0483: } else if (aQuery instanceof ReportQueryByCriteria) {
0484: return getReportQueryByCriteriaCount((ReportQueryByCriteria) aQuery);
0485: } else {
0486: return getQueryByCriteriaCount((QueryByCriteria) aQuery);
0487: }
0488: }
0489:
0490: /**
0491: * Create a Count-Query for QueryBySQL
0492: *
0493: * @param aQuery
0494: * @return The count query
0495: */
0496: private Query getQueryBySqlCount(QueryBySQL aQuery) {
0497: String countSql = aQuery.getSql();
0498:
0499: int fromPos = countSql.toUpperCase().indexOf(" FROM ");
0500: if (fromPos >= 0) {
0501: countSql = "select count(*)" + countSql.substring(fromPos);
0502: }
0503:
0504: int orderPos = countSql.toUpperCase().indexOf(" ORDER BY ");
0505: if (orderPos >= 0) {
0506: countSql = countSql.substring(0, orderPos);
0507: }
0508:
0509: return new QueryBySQL(aQuery.getSearchClass(), countSql);
0510: }
0511:
0512: /**
0513: * Create a Count-Query for QueryByCriteria
0514: */
0515: private Query getQueryByCriteriaCount(QueryByCriteria aQuery) {
0516: Class searchClass = aQuery.getSearchClass();
0517: ReportQueryByCriteria countQuery = null;
0518: Criteria countCrit = null;
0519: String[] columns = new String[1];
0520:
0521: // BRJ: copied Criteria without groupby, orderby, and prefetched relationships
0522: if (aQuery.getCriteria() != null) {
0523: countCrit = aQuery.getCriteria().copy(false, false, false);
0524: }
0525:
0526: if (aQuery.isDistinct()) {
0527: // BRJ: Count distinct is dbms dependent
0528: // hsql/sapdb: select count (distinct(person_id || project_id)) from person_project
0529: // mysql: select count (distinct person_id,project_id) from person_project
0530: // [tomdz]
0531: // Some databases have no support for multi-column count distinct (e.g. Derby)
0532: // Here we use a SELECT count(*) FROM (SELECT DISTINCT ...) instead
0533: //
0534: // concatenation of pk-columns is a simple way to obtain a single column
0535: // but concatenation is also dbms dependent:
0536: //
0537: // SELECT count(distinct concat(row1, row2, row3)) mysql
0538: // SELECT count(distinct (row1 || row2 || row3)) ansi
0539: // SELECT count(distinct (row1 + row2 + row3)) ms sql-server
0540:
0541: FieldDescriptor[] pkFields = m_broker.getClassDescriptor(
0542: searchClass).getPkFields();
0543: String[] keyColumns = new String[pkFields.length];
0544:
0545: if (pkFields.length > 1) {
0546: // TODO: Use ColumnName. This is a temporary solution because
0547: // we cannot yet resolve multiple columns in the same attribute.
0548: for (int idx = 0; idx < pkFields.length; idx++) {
0549: keyColumns[idx] = pkFields[idx].getColumnName();
0550: }
0551: } else {
0552: for (int idx = 0; idx < pkFields.length; idx++) {
0553: keyColumns[idx] = pkFields[idx].getAttributeName();
0554: }
0555: }
0556: // [tomdz]
0557: // TODO: Add support for databases that do not support COUNT DISTINCT over multiple columns
0558: // if (getPlatform().supportsMultiColumnCountDistinct())
0559: // {
0560: // columns[0] = "count(distinct " + getPlatform().concatenate(keyColumns) + ")";
0561: // }
0562: // else
0563: // {
0564: // columns = keyColumns;
0565: // }
0566:
0567: columns[0] = "count(distinct "
0568: + getPlatform().concatenate(keyColumns) + ")";
0569: } else {
0570: columns[0] = "count(*)";
0571: }
0572:
0573: // BRJ: we have to preserve indirection table !
0574: if (aQuery instanceof MtoNQuery) {
0575: MtoNQuery mnQuery = (MtoNQuery) aQuery;
0576: ReportQueryByMtoNCriteria mnReportQuery = new ReportQueryByMtoNCriteria(
0577: searchClass, columns, countCrit);
0578:
0579: mnReportQuery.setIndirectionTable(mnQuery
0580: .getIndirectionTable());
0581: countQuery = mnReportQuery;
0582: } else {
0583: countQuery = new ReportQueryByCriteria(searchClass,
0584: columns, countCrit);
0585: }
0586:
0587: // BRJ: we have to preserve outer-join-settings (by André Markwalder)
0588: for (Iterator outerJoinPath = aQuery.getOuterJoinPaths()
0589: .iterator(); outerJoinPath.hasNext();) {
0590: String path = (String) outerJoinPath.next();
0591:
0592: if (aQuery.isPathOuterJoin(path)) {
0593: countQuery.setPathOuterJoin(path);
0594: }
0595: }
0596:
0597: //BRJ: add orderBy Columns asJoinAttributes
0598: List orderBy = aQuery.getOrderBy();
0599:
0600: if ((orderBy != null) && !orderBy.isEmpty()) {
0601: String[] joinAttributes = new String[orderBy.size()];
0602:
0603: for (int idx = 0; idx < orderBy.size(); idx++) {
0604: joinAttributes[idx] = ((FieldHelper) orderBy.get(idx)).name;
0605: }
0606: countQuery.setJoinAttributes(joinAttributes);
0607: }
0608:
0609: // [tomdz]
0610: // TODO:
0611: // For those databases that do not support COUNT DISTINCT over multiple columns
0612: // we wrap the normal SELECT DISTINCT that we just created, into a SELECT count(*)
0613: // For this however we need a report query that gets its data from a sub query instead
0614: // of a table (target class)
0615: // if (aQuery.isDistinct() && !getPlatform().supportsMultiColumnCountDistinct())
0616: // {
0617: // }
0618:
0619: return countQuery;
0620: }
0621:
0622: /**
0623: * Create a Count-Query for ReportQueryByCriteria
0624: */
0625: private Query getReportQueryByCriteriaCount(
0626: ReportQueryByCriteria aQuery) {
0627: ReportQueryByCriteria countQuery = (ReportQueryByCriteria) getQueryByCriteriaCount(aQuery);
0628:
0629: // BRJ: keep the original columns to build the Join
0630: countQuery.setJoinAttributes(aQuery.getAttributes());
0631:
0632: // BRJ: we have to preserve groupby information
0633: Iterator iter = aQuery.getGroupBy().iterator();
0634: while (iter.hasNext()) {
0635: countQuery.addGroupBy((FieldHelper) iter.next());
0636: }
0637:
0638: return countQuery;
0639: }
0640:
0641: /**
0642: * answer the platform
0643: *
0644: * @return the platform
0645: */
0646: private Platform getPlatform() {
0647: return m_broker.serviceSqlGenerator().getPlatform();
0648: }
0649:
0650: /*
0651: NOTE: use weak key references to allow reclaiming
0652: of no longer used ClassDescriptor instances
0653: */
0654: private Map sqlSelectMap = new ReferenceIdentityMap(
0655: ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD);
0656:
0657: /**
0658: * TODO: This method should be moved to {@link org.apache.ojb.broker.accesslayer.JdbcAccess}
0659: * before 1.1 release.
0660: *
0661: * This method checks if the requested object can be
0662: * found in database (without object materialization).
0663: *
0664: * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the
0665: * object/{@link org.apache.ojb.broker.Identity} to check.
0666: * @param obj The <em>object</em> to check.
0667: * @param oid The associated {@link org.apache.ojb.broker.Identity}.
0668: * {@link org.apache.ojb.broker.Identity} of the object
0669: * @return Return <em>true</em> if the object is already persisted, <em>false</em> if the object is transient.
0670: */
0671: public boolean doesExist(ClassDescriptor cld, Identity oid,
0672: Object obj) {
0673: boolean result = false;
0674: String sql = (String) sqlSelectMap.get(cld);
0675: if (sql == null) {
0676: sql = new SqlExistStatement(cld, LoggerFactory
0677: .getDefaultLogger()).getStatement();
0678: sqlSelectMap.put(cld, sql);
0679: }
0680: ValueContainer[] pkValues;
0681: if (oid == null) {
0682: pkValues = getKeyValues(cld, obj, true);
0683: } else {
0684: pkValues = getKeyValues(cld, oid);
0685: }
0686: StatementManagerIF sm = m_broker.serviceStatementManager();
0687: PreparedStatement stmt = null;
0688: ResultSet rs = null;
0689: try {
0690: stmt = sm.getPreparedStatement(cld, sql, false, 1, false);
0691: sm.bindValues(stmt, pkValues, 1);
0692: rs = stmt.executeQuery();
0693: result = rs.next();
0694: } catch (SQLException e) {
0695: throw ExceptionHelper.generateException(
0696: "[BrokerHelper#doesExist] Can't check if specified"
0697: + " object is already persisted", e, sql,
0698: cld, pkValues, null, obj);
0699: } finally {
0700: sm.closeResources(stmt, rs);
0701: }
0702:
0703: return result;
0704: }
0705:
0706: /**
0707: * This method concatenate the main object with all reference
0708: * objects (1:1, 1:n and m:n) by hand. This method is needed when
0709: * in the reference metadata definitions the auto-xxx setting was disabled.
0710: * More info see OJB doc.
0711: */
0712: public void link(Object obj, boolean insert) {
0713: linkOrUnlink(true, obj, insert);
0714: }
0715:
0716: /**
0717: * Unlink all references from this object.
0718: * More info see OJB doc.
0719: * @param obj Object with reference
0720: */
0721: public void unlink(Object obj) {
0722: linkOrUnlink(false, obj, false);
0723: }
0724:
0725: private void linkOrUnlink(boolean doLink, Object obj, boolean insert) {
0726: ClassDescriptor cld = m_broker.getDescriptorRepository()
0727: .getDescriptorFor(obj.getClass());
0728:
0729: if (cld.getObjectReferenceDescriptors().size() > 0) {
0730: // never returns null, thus we can direct call iterator
0731: Iterator descriptors = cld.getObjectReferenceDescriptors()
0732: .iterator();
0733: while (descriptors.hasNext()) {
0734: ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) descriptors
0735: .next();
0736: linkOrUnlinkOneToOne(doLink, obj, ord, insert);
0737: }
0738: }
0739: if (cld.getCollectionDescriptors().size() > 0) {
0740: // never returns null, thus we can direct call iterator
0741: Iterator descriptors = cld.getCollectionDescriptors()
0742: .iterator();
0743: while (descriptors.hasNext()) {
0744: CollectionDescriptor cod = (CollectionDescriptor) descriptors
0745: .next();
0746: linkOrUnlinkXToMany(doLink, obj, cod, insert);
0747: }
0748: }
0749: }
0750:
0751: /**
0752: * This method concatenate the main object and the specified reference
0753: * object (1:1 reference a referenced object, 1:n and m:n reference a
0754: * collection of referenced objects) by hand. This method is needed when
0755: * in the reference metadata definitions the auto-xxx setting was disabled.
0756: * More info see OJB doc.
0757: *
0758: * @param obj Object with reference
0759: * @param ord the ObjectReferenceDescriptor of the reference
0760: * @param insert flag signals insert operation
0761: */
0762: public void link(Object obj, ObjectReferenceDescriptor ord,
0763: boolean insert) {
0764: linkOrUnlink(true, obj, ord, insert);
0765: }
0766:
0767: /**
0768: * This method concatenate the main object and the specified reference
0769: * object (1:1 reference a referenced object, 1:n and m:n reference a
0770: * collection of referenced objects) by hand. This method is needed when
0771: * in the reference metadata definitions the auto-xxx setting was disabled.
0772: * More info see OJB doc.
0773: *
0774: * @param obj Object with reference
0775: * @param attributeName field name of the reference
0776: * @param insert flag signals insert operation
0777: * @return true if the specified reference was found and linking was successful
0778: */
0779: public boolean link(Object obj, String attributeName, boolean insert) {
0780: return linkOrUnlink(true, obj, attributeName, insert);
0781: }
0782:
0783: /**
0784: * This method concatenate the main object and the specified reference
0785: * object (1:1 reference a referenced object, 1:n and m:n reference a
0786: * collection of referenced objects) by hand. This method is needed when
0787: * in the reference metadata definitions the auto-xxx setting was disabled.
0788: * More info see OJB doc.
0789: *
0790: * @param obj Object with reference
0791: * @param attributeName field name of the reference
0792: * @param reference The referenced object
0793: * @param insert flag signals insert operation
0794: * @return true if the specified reference was found and linking was successful
0795: */
0796: public boolean link(Object obj, String attributeName,
0797: Object reference, boolean insert) {
0798: ClassDescriptor cld = m_broker.getDescriptorRepository()
0799: .getDescriptorFor(ProxyHelper.getRealClass(obj));
0800: ObjectReferenceDescriptor ord;
0801: boolean match = false;
0802: // first look for reference then for collection
0803: ord = cld.getObjectReferenceDescriptorByName(attributeName);
0804: if (ord != null) {
0805: linkOrUnlinkOneToOne(true, obj, ord, insert);
0806: match = true;
0807: } else {
0808: CollectionDescriptor cod = cld
0809: .getCollectionDescriptorByName(attributeName);
0810: if (cod != null) {
0811: linkOrUnlinkXToMany(true, obj, cod, insert);
0812: match = true;
0813: }
0814: }
0815: return match;
0816: }
0817:
0818: /**
0819: * Unlink the specified reference object.
0820: * More info see OJB doc.
0821: * @param source The source object with the specified reference field.
0822: * @param attributeName The field name of the reference to unlink.
0823: * @param target The referenced object to unlink.
0824: */
0825: public boolean unlink(Object source, String attributeName,
0826: Object target) {
0827: return linkOrUnlink(false, source, attributeName, false);
0828: }
0829:
0830: /**
0831: * Unlink all referenced objects of the specified field.
0832: * More info see OJB doc.
0833: * @param source The source object with the specified reference.
0834: * @param attributeName The field name of the reference to unlink.
0835: */
0836: public boolean unlink(Object source, String attributeName) {
0837: return linkOrUnlink(false, source, attributeName, false);
0838: }
0839:
0840: /**
0841: * Unlink the specified reference from this object.
0842: * More info see OJB doc.
0843: *
0844: * @param obj Object with reference
0845: * @param ord the ObjectReferenceDescriptor of the reference
0846: * @param insert flag signals insert operation
0847: */
0848: public void unlink(Object obj, ObjectReferenceDescriptor ord,
0849: boolean insert) {
0850: linkOrUnlink(false, obj, ord, insert);
0851: }
0852:
0853: private boolean linkOrUnlink(boolean doLink, Object obj,
0854: String attributeName, boolean insert) {
0855: boolean match = false;
0856: ClassDescriptor cld = m_broker.getDescriptorRepository()
0857: .getDescriptorFor(ProxyHelper.getRealClass(obj));
0858: ObjectReferenceDescriptor ord;
0859:
0860: // first look for reference then for collection
0861: ord = cld.getObjectReferenceDescriptorByName(attributeName);
0862: if (ord != null) {
0863: linkOrUnlinkOneToOne(doLink, obj, ord, insert);
0864: match = true;
0865: } else {
0866: CollectionDescriptor cod = cld
0867: .getCollectionDescriptorByName(attributeName);
0868: if (cod != null) {
0869: linkOrUnlinkXToMany(doLink, obj, cod, insert);
0870: match = true;
0871: }
0872: }
0873:
0874: return match;
0875: }
0876:
0877: private void linkOrUnlink(boolean doLink, Object obj,
0878: ObjectReferenceDescriptor ord, boolean insert) {
0879: if (ord instanceof CollectionDescriptor) {
0880: linkOrUnlinkXToMany(doLink, obj,
0881: (CollectionDescriptor) ord, insert);
0882: } else {
0883: linkOrUnlinkOneToOne(doLink, obj, ord, insert);
0884: }
0885: }
0886:
0887: private void linkOrUnlinkXToMany(boolean doLink, Object obj,
0888: CollectionDescriptor cod, boolean insert) {
0889: if (doLink) {
0890: if (cod.isMtoNRelation()) {
0891: m_broker.linkMtoN(obj, cod, insert);
0892: } else {
0893: m_broker.linkOneToMany(obj, cod, insert);
0894: }
0895: } else {
0896: m_broker.unlinkXtoN(obj, cod);
0897: }
0898: }
0899:
0900: private void linkOrUnlinkOneToOne(boolean doLink, Object obj,
0901: ObjectReferenceDescriptor ord, boolean insert) {
0902: /*
0903: arminw: we need the class-descriptor where the reference is declared, thus we ask the
0904: reference-descriptor for this, instead of using the class-descriptor of the specified
0905: object. If the reference was declared within an interface (should never happen) we
0906: only can use the descriptor of the real class.
0907: */
0908: ClassDescriptor cld = ord.getClassDescriptor();
0909: if (cld.isInterface()) {
0910: cld = m_broker.getDescriptorRepository().getDescriptorFor(
0911: ProxyHelper.getRealClass(obj));
0912: }
0913:
0914: if (doLink) {
0915: m_broker.linkOneToOne(obj, cld, ord, insert);
0916: } else {
0917: m_broker.unlinkFK(obj, cld, ord);
0918: // in 1:1 relation we have to set relation to null
0919: ord.getPersistentField().set(obj, null);
0920: }
0921: }
0922:
0923: /**
0924: * Unlink a bunch of 1:n or m:n objects.
0925: *
0926: * @param source The source object with reference.
0927: * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
0928: * @param referencesToUnlink List of referenced objects to unlink.
0929: */
0930: public void unlink(Object source, CollectionDescriptor cds,
0931: List referencesToUnlink) {
0932: for (int i = 0; i < referencesToUnlink.size(); i++) {
0933: unlink(source, cds, referencesToUnlink.get(i));
0934: }
0935: }
0936:
0937: /**
0938: * Unlink a single 1:n or m:n object.
0939: *
0940: * @param source The source object with reference.
0941: * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
0942: * @param referenceToUnlink The referenced object to link.
0943: */
0944: public void unlink(Object source, CollectionDescriptor cds,
0945: Object referenceToUnlink) {
0946: if (cds.isMtoNRelation()) {
0947: m_broker.deleteMtoNImplementor(new MtoNImplementor(cds,
0948: source, referenceToUnlink));
0949: } else {
0950: ClassDescriptor cld = m_broker
0951: .getClassDescriptor(referenceToUnlink.getClass());
0952: m_broker.unlinkFK(referenceToUnlink, cld, cds);
0953: }
0954: }
0955:
0956: /**
0957: * Link a bunch of 1:n or m:n objects.
0958: *
0959: * @param source The source object with reference.
0960: * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation.
0961: * @param referencesToLink List of referenced objects to link.
0962: */
0963: public void link(Object source, CollectionDescriptor cds,
0964: List referencesToLink) {
0965: for (int i = 0; i < referencesToLink.size(); i++) {
0966: link(source, cds, referencesToLink.get(i));
0967: }
0968: }
0969:
0970: /**
0971: * Link a single 1:n or m:n object.
0972: *
0973: * @param source The source object with the declared reference.
0974: * @param cds The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} of the relation declared in source object.
0975: * @param referenceToLink The referenced object to link.
0976: */
0977: public void link(Object source, CollectionDescriptor cds,
0978: Object referenceToLink) {
0979: if (cds.isMtoNRelation()) {
0980: m_broker.addMtoNImplementor(new MtoNImplementor(cds,
0981: source, referenceToLink));
0982: } else {
0983: ClassDescriptor cld = m_broker
0984: .getClassDescriptor(referenceToLink.getClass());
0985: m_broker.link(referenceToLink, cld, cds, source, false);
0986: }
0987: }
0988:
0989: /**
0990: * Returns an Iterator instance for {@link java.util.Collection}, object Array or
0991: * {@link org.apache.ojb.broker.ManageableCollection} instances.
0992: *
0993: * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
0994: * Array or {@link org.apache.ojb.broker.ManageableCollection}.
0995: * @return Iterator able to handle given collection object
0996: */
0997: public static Iterator getCollectionIterator(
0998: Object collectionOrArray) {
0999: Iterator colIterator;
1000: if (collectionOrArray instanceof ManageableCollection) {
1001: colIterator = ((ManageableCollection) collectionOrArray)
1002: .ojbIterator();
1003: } else if (collectionOrArray instanceof Collection) {
1004: colIterator = ((Collection) collectionOrArray).iterator();
1005: } else if (collectionOrArray.getClass().isArray()) {
1006: colIterator = new ArrayIterator(collectionOrArray);
1007: } else {
1008: throw new OJBRuntimeException(
1009: "Given object collection of type '"
1010: + (collectionOrArray != null ? collectionOrArray
1011: .getClass().toString()
1012: : "null")
1013: + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
1014: }
1015: return colIterator;
1016: }
1017:
1018: /**
1019: * Returns an object array for {@link java.util.Collection}, array or
1020: * {@link org.apache.ojb.broker.ManageableCollection} instances.
1021: *
1022: * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
1023: * Array or {@link org.apache.ojb.broker.ManageableCollection}.
1024: * @return Object array able to handle given collection or array object
1025: */
1026: public static Object[] getCollectionArray(Object collectionOrArray) {
1027: Object[] result;
1028: if (collectionOrArray instanceof Collection) {
1029: result = ((Collection) collectionOrArray).toArray();
1030: } else if (collectionOrArray instanceof ManageableCollection) {
1031: Collection newCol = new ArrayList();
1032: CollectionUtils.addAll(newCol,
1033: ((ManageableCollection) collectionOrArray)
1034: .ojbIterator());
1035: result = newCol.toArray();
1036: } else if (collectionOrArray.getClass().isArray()) {
1037: result = (Object[]) collectionOrArray;
1038: } else {
1039: throw new OJBRuntimeException(
1040: "Given object collection of type '"
1041: + (collectionOrArray != null ? collectionOrArray
1042: .getClass().toString()
1043: : "null")
1044: + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
1045: }
1046: return result;
1047: }
1048:
1049: /**
1050: * Returns <em>true</em> if one or more anonymous FK fields are used.
1051: * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the main object.
1052: * @param rds The {@link org.apache.ojb.broker.metadata.ObjectReferenceDescriptor} of the referenced object.
1053: * @return <em>true</em> if one or more anonymous FK fields are used for specified reference.
1054: */
1055: public static boolean hasAnonymousKeyReference(ClassDescriptor cld,
1056: ObjectReferenceDescriptor rds) {
1057: boolean result = false;
1058: FieldDescriptor[] fkFields = rds
1059: .getForeignKeyFieldDescriptors(cld);
1060: for (int i = 0; i < fkFields.length; i++) {
1061: FieldDescriptor fkField = fkFields[i];
1062: if (fkField.isAnonymous()) {
1063: result = true;
1064: break;
1065: }
1066: }
1067: return result;
1068: }
1069:
1070: // /**
1071: // * Use this method to extract the {@link org.apache.ojb.broker.metadata.ClassDescriptor} where
1072: // * the {@link org.apache.ojb.broker.metadata.ObjectReferenceDescriptor reference} is declared.
1073: // * It's possible that the reference is declared in a super-class.
1074: // * @param broker
1075: // * @param reference
1076: // * @param source
1077: // * @return
1078: // */
1079: // public static ClassDescriptor extractDescriptorForReference(PersistenceBroker broker, ObjectReferenceDescriptor reference, Object source)
1080: // {
1081: // /*
1082: // arminw: we need the class-descriptor where the reference is declared, thus we ask the
1083: // reference-descriptor for this, instead of using the class-descriptor of the specified
1084: // object. If the reference was declared within an interface (should never happen) we
1085: // only can use the descriptor of the real class.
1086: // */
1087: // ClassDescriptor cld = reference.getClassDescriptor();
1088: // if(cld.isInterface())
1089: // {
1090: // cld = broker.getDescriptorRepository().getDescriptorFor(ProxyHelper.getRealClass(source));
1091: // }
1092: // return cld;
1093: // }
1094:
1095: // /**
1096: // * Returns a {@link java.util.List} instance of the specified object in method argument,
1097: // * in which the argument must be of type {@link java.util.Collection}, array or
1098: // * {@link org.apache.ojb.broker.ManageableCollection}.
1099: // *
1100: // * @param collectionOrArray a none <em>null</em> object of type {@link java.util.Collection},
1101: // * Array or {@link org.apache.ojb.broker.ManageableCollection}.
1102: // * @return Object array able to handle given collection or array object
1103: // */
1104: // public static List getCollectionList(Object collectionOrArray)
1105: // {
1106: // List result = null;
1107: // if (collectionOrArray instanceof Collection)
1108: // {
1109: // result = ((Collection) collectionOrArray).toArray();
1110: // }
1111: // else if (collectionOrArray instanceof ManageableCollection)
1112: // {
1113: // Collection newCol = new ArrayList();
1114: // CollectionUtils.addAll(newCol, ((ManageableCollection) collectionOrArray).ojbIterator());
1115: // result = newCol.toArray();
1116: // }
1117: // else if (collectionOrArray.getClass().isArray())
1118: // {
1119: // result = (Object[]) collectionOrArray;
1120: // }
1121: // else
1122: // {
1123: // throw new OJBRuntimeException( "Given object collection of type '"
1124: // + (collectionOrArray != null ? collectionOrArray.getClass().toString() : "null")
1125: // + "' can not be managed by OJB. Use Array, Collection or ManageableCollection instead!");
1126: // }
1127: // return result;
1128: // }
1129: }
|