001: package org.apache.ojb.broker.core;
002:
003: /* Copyright 2003-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.util.ArrayList;
019: import java.util.Collection;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.sql.SQLException;
023:
024: import org.apache.commons.lang.builder.EqualsBuilder;
025: import org.apache.commons.lang.builder.HashCodeBuilder;
026: import org.apache.commons.lang.builder.ToStringBuilder;
027: import org.apache.commons.lang.ArrayUtils;
028: import org.apache.ojb.broker.MtoNImplementor;
029: import org.apache.ojb.broker.OJBRuntimeException;
030: import org.apache.ojb.broker.PersistenceBrokerException;
031: import org.apache.ojb.broker.PersistenceBrokerSQLException;
032: import org.apache.ojb.broker.accesslayer.ResultSetAndStatement;
033: import org.apache.ojb.broker.core.proxy.ProxyHelper;
034: import org.apache.ojb.broker.metadata.ClassDescriptor;
035: import org.apache.ojb.broker.metadata.CollectionDescriptor;
036: import org.apache.ojb.broker.metadata.DescriptorRepository;
037: import org.apache.ojb.broker.metadata.FieldDescriptor;
038: import org.apache.ojb.broker.metadata.JdbcType;
039: import org.apache.ojb.broker.query.Query;
040: import org.apache.ojb.broker.util.logging.Logger;
041: import org.apache.ojb.broker.util.logging.LoggerFactory;
042:
043: /**
044: * Manage all stuff related to non-decomposed M:N association.
045: *
046: * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
047: * @author <a href="mailto:leandro@ibnetwork.com.br">Leandro Rodrigo Saad Cruz<a>
048: * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird<a>
049: * @author <a href="mailto:jbraeuchi@hotmail.com">Jakob Braeuchi</a>
050: * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
051: * @version $Id: MtoNBroker.java,v 1.10.2.9 2005/12/21 22:25:00 tomdz Exp $
052: */
053: public class MtoNBroker {
054: private Logger log = LoggerFactory.getLogger(MtoNBroker.class);
055:
056: private PersistenceBrokerImpl pb;
057: /**
058: * Used to store {@link GenericObject} while transaction running, used as
059: * workaround for m:n insert problem.
060: * TODO: find better solution for m:n handling
061: */
062: private List tempObjects = new ArrayList();
063:
064: public MtoNBroker(final PersistenceBrokerImpl broker) {
065: this .pb = broker;
066: }
067:
068: public void reset() {
069: tempObjects.clear();
070: }
071:
072: /**
073: * Stores new values of a M:N association in a indirection table.
074: *
075: * @param cod The {@link org.apache.ojb.broker.metadata.CollectionDescriptor} for the m:n relation
076: * @param realObject The real object
077: * @param otherObj The referenced object
078: * @param mnKeys The all {@link org.apache.ojb.broker.core.MtoNBroker.Key} matching the real object
079: */
080: public void storeMtoNImplementor(CollectionDescriptor cod,
081: Object realObject, Object otherObj, Collection mnKeys) {
082: ClassDescriptor cld = pb.getDescriptorRepository()
083: .getDescriptorFor(realObject.getClass());
084: ValueContainer[] pkValues = pb.serviceBrokerHelper()
085: .getKeyValues(cld, realObject);
086: String[] pkColumns = cod.getFksToThisClass();
087:
088: ClassDescriptor otherCld = pb.getDescriptorRepository()
089: .getDescriptorFor(ProxyHelper.getRealClass(otherObj));
090: ValueContainer[] otherPkValues = pb.serviceBrokerHelper()
091: .getKeyValues(otherCld, otherObj);
092:
093: String[] otherPkColumns = cod.getFksToItemClass();
094: String table = cod.getIndirectionTable();
095: MtoNBroker.Key key = new MtoNBroker.Key(otherPkValues);
096:
097: if (mnKeys.contains(key)) {
098: return;
099: }
100:
101: /*
102: fix for OJB-76, composite M & N keys that have some fields common
103: find the "shared" indirection table columns, values and remove these from m- or n- side
104: */
105: for (int i = 0; i < otherPkColumns.length; i++) {
106: int index = ArrayUtils
107: .indexOf(pkColumns, otherPkColumns[i]);
108: if (index != -1) {
109: // shared indirection table column found, remove this column from one side
110: pkColumns = (String[]) ArrayUtils.remove(pkColumns,
111: index);
112: // remove duplicate value too
113: pkValues = (ValueContainer[]) ArrayUtils.remove(
114: pkValues, index);
115: }
116: }
117:
118: String[] cols = mergeColumns(pkColumns, otherPkColumns);
119: String insertStmt = pb.serviceSqlGenerator()
120: .getInsertMNStatement(table, pkColumns, otherPkColumns);
121: ValueContainer[] values = mergeContainer(pkValues,
122: otherPkValues);
123: GenericObject gObj = new GenericObject(table, cols, values);
124: if (!tempObjects.contains(gObj)) {
125: pb.serviceJdbcAccess().executeUpdateSQL(insertStmt, cld,
126: pkValues, otherPkValues);
127: tempObjects.add(gObj);
128: }
129: }
130:
131: /**
132: * get a Collection of Keys of already existing m:n rows
133: *
134: * @param cod
135: * @param obj
136: * @return Collection of Key
137: */
138: public List getMtoNImplementor(CollectionDescriptor cod, Object obj) {
139: ResultSetAndStatement rs = null;
140: ArrayList result = new ArrayList();
141: ClassDescriptor cld = pb.getDescriptorRepository()
142: .getDescriptorFor(obj.getClass());
143: ValueContainer[] pkValues = pb.serviceBrokerHelper()
144: .getKeyValues(cld, obj);
145: String[] pkColumns = cod.getFksToThisClass();
146: String[] fkColumns = cod.getFksToItemClass();
147: String table = cod.getIndirectionTable();
148:
149: String selectStmt = pb.serviceSqlGenerator()
150: .getSelectMNStatement(table, fkColumns, pkColumns);
151:
152: ClassDescriptor itemCLD = pb.getDescriptorRepository()
153: .getDescriptorFor(cod.getItemClass());
154: Collection extents = pb.getDescriptorRepository()
155: .getAllConcreteSubclassDescriptors(itemCLD);
156: if (extents.size() > 0) {
157: itemCLD = (ClassDescriptor) extents.iterator().next();
158: }
159: FieldDescriptor[] itemClassPKFields = itemCLD.getPkFields();
160: if (itemClassPKFields.length != fkColumns.length) {
161: throw new PersistenceBrokerException(
162: "All pk fields of the element-class need to"
163: + " be declared in the indirection table. Element class is "
164: + itemCLD.getClassNameOfObject()
165: + " with "
166: + itemClassPKFields.length
167: + " pk-fields."
168: + " Declared 'fk-pointing-to-element-class' elements in collection-descriptor are"
169: + fkColumns.length);
170: }
171: try {
172: rs = pb.serviceJdbcAccess().executeSQL(selectStmt, cld,
173: pkValues, Query.NOT_SCROLLABLE);
174: while (rs.m_rs.next()) {
175: ValueContainer[] row = new ValueContainer[fkColumns.length];
176: for (int i = 0; i < row.length; i++) {
177: row[i] = new ValueContainer(rs.m_rs
178: .getObject(i + 1), itemClassPKFields[i]
179: .getJdbcType());
180: }
181: result.add(new MtoNBroker.Key(row));
182: }
183: } catch (PersistenceBrokerException e) {
184: throw e;
185: } catch (SQLException e) {
186: throw new PersistenceBrokerSQLException(e);
187: } finally {
188: if (rs != null)
189: rs.close();
190: }
191: return result;
192: }
193:
194: /**
195: * delete all rows from m:n table belonging to obj
196: *
197: * @param cod
198: * @param obj
199: */
200: public void deleteMtoNImplementor(CollectionDescriptor cod,
201: Object obj) {
202: ClassDescriptor cld = pb.getDescriptorRepository()
203: .getDescriptorFor(obj.getClass());
204: ValueContainer[] pkValues = pb.serviceBrokerHelper()
205: .getKeyValues(cld, obj);
206: String[] pkColumns = cod.getFksToThisClass();
207: String table = cod.getIndirectionTable();
208: String deleteStmt = pb.serviceSqlGenerator()
209: .getDeleteMNStatement(table, pkColumns, null);
210: pb.serviceJdbcAccess().executeUpdateSQL(deleteStmt, cld,
211: pkValues, null);
212: }
213:
214: /**
215: * deletes all rows from m:n table that are not used in relatedObjects
216: *
217: * @param cod
218: * @param obj
219: * @param collectionIterator
220: * @param mnKeys
221: */
222: public void deleteMtoNImplementor(CollectionDescriptor cod,
223: Object obj, Iterator collectionIterator, Collection mnKeys) {
224: if (mnKeys.isEmpty() || collectionIterator == null) {
225: return;
226: }
227: List workList = new ArrayList(mnKeys);
228: MtoNBroker.Key relatedObjKeys;
229: ClassDescriptor relatedCld = pb.getDescriptorRepository()
230: .getDescriptorFor(cod.getItemClass());
231: Object relatedObj;
232:
233: // remove keys of relatedObject from the existing m:n rows in workList
234: while (collectionIterator.hasNext()) {
235: relatedObj = collectionIterator.next();
236: relatedObjKeys = new MtoNBroker.Key(pb
237: .serviceBrokerHelper().getKeyValues(relatedCld,
238: relatedObj, true));
239: workList.remove(relatedObjKeys);
240: }
241:
242: // delete all remaining keys in workList
243: ClassDescriptor cld = pb.getDescriptorRepository()
244: .getDescriptorFor(obj.getClass());
245: ValueContainer[] pkValues = pb.serviceBrokerHelper()
246: .getKeyValues(cld, obj);
247:
248: String[] pkColumns = cod.getFksToThisClass();
249: String[] fkColumns = cod.getFksToItemClass();
250: String table = cod.getIndirectionTable();
251: String deleteStmt;
252:
253: ValueContainer[] fkValues;
254: Iterator iter = workList.iterator();
255: while (iter.hasNext()) {
256: fkValues = ((MtoNBroker.Key) iter.next()).m_containers;
257: deleteStmt = pb.serviceSqlGenerator().getDeleteMNStatement(
258: table, pkColumns, fkColumns);
259: pb.serviceJdbcAccess().executeUpdateSQL(deleteStmt, cld,
260: pkValues, fkValues);
261: }
262: }
263:
264: /**
265: * @param m2n
266: */
267: public void storeMtoNImplementor(MtoNImplementor m2n) {
268: if (log.isDebugEnabled())
269: log.debug("Storing M2N implementor [" + m2n + "]");
270: insertOrDeleteMtoNImplementor(m2n, true);
271: }
272:
273: /**
274: * @param m2n
275: */
276: public void deleteMtoNImplementor(MtoNImplementor m2n) {
277: if (log.isDebugEnabled())
278: log.debug("Deleting M2N implementor [" + m2n + "]");
279: insertOrDeleteMtoNImplementor(m2n, false);
280: }
281:
282: /**
283: * @see org.apache.ojb.broker.PersistenceBroker#deleteMtoNImplementor
284: */
285: private void insertOrDeleteMtoNImplementor(MtoNImplementor m2nImpl,
286: boolean insert) throws PersistenceBrokerException {
287: //look for a collection descriptor on left such as left.element-class-ref='right'
288: DescriptorRepository dr = pb.getDescriptorRepository();
289:
290: Object leftObject = m2nImpl.getLeftObject();
291: Class leftClass = m2nImpl.getLeftClass();
292: Object rightObject = m2nImpl.getRightObject();
293: Class rightClass = m2nImpl.getRightClass();
294:
295: //are written per class, maybe referencing abstract classes or interfaces
296: //so let's look for collection descriptors on the left class and try to
297: // handle extents on teh right class
298: ClassDescriptor leftCld = dr.getDescriptorFor(leftClass);
299: ClassDescriptor rightCld = dr.getDescriptorFor(rightClass);
300: //Vector leftColds = leftCld.getCollectionDescriptors();
301: CollectionDescriptor wanted = m2nImpl.getLeftDescriptor();
302:
303: if (leftObject == null || rightObject == null) {
304: //TODO: to be implemented, must change MtoNImplementor
305: //deleteMtoNImplementor(wanted,leftObject) || deleteMtoNImplementor(wanted,rightObject)
306: log
307: .error("Can't handle MtoNImplementor in correct way, found a 'null' object");
308: } else {
309: //delete only one row
310: ValueContainer[] leftPkValues = pb.serviceBrokerHelper()
311: .getKeyValues(leftCld, leftObject);
312: ValueContainer[] rightPkValues = pb.serviceBrokerHelper()
313: .getKeyValues(rightCld, rightObject);
314: String[] pkLeftColumns = wanted.getFksToThisClass();
315: String[] pkRightColumns = wanted.getFksToItemClass();
316: String table = wanted.getIndirectionTable();
317: if (table == null)
318: throw new PersistenceBrokerException(
319: "Can't remove MtoN implementor without an indirection table");
320:
321: String stmt;
322: String[] cols = mergeColumns(pkLeftColumns, pkRightColumns);
323: ValueContainer[] values = mergeContainer(leftPkValues,
324: rightPkValues);
325: if (insert) {
326: stmt = pb.serviceSqlGenerator().getInsertMNStatement(
327: table, pkLeftColumns, pkRightColumns);
328: GenericObject gObj = new GenericObject(table, cols,
329: values);
330: if (!tempObjects.contains(gObj)) {
331: pb.serviceJdbcAccess().executeUpdateSQL(stmt,
332: leftCld, leftPkValues, rightPkValues);
333: tempObjects.add(gObj);
334: }
335: } else {
336: stmt = pb.serviceSqlGenerator().getDeleteMNStatement(
337: table, pkLeftColumns, pkRightColumns);
338: pb.serviceJdbcAccess().executeUpdateSQL(stmt, leftCld,
339: leftPkValues, rightPkValues);
340: }
341: }
342: }
343:
344: private String[] mergeColumns(String[] first, String[] second) {
345: String[] cols = new String[first.length + second.length];
346: System.arraycopy(first, 0, cols, 0, first.length);
347: System.arraycopy(second, 0, cols, first.length, second.length);
348: return cols;
349: }
350:
351: private ValueContainer[] mergeContainer(ValueContainer[] first,
352: ValueContainer[] second) {
353: ValueContainer[] values = new ValueContainer[first.length
354: + second.length];
355: System.arraycopy(first, 0, values, 0, first.length);
356: System
357: .arraycopy(second, 0, values, first.length,
358: second.length);
359: return values;
360: }
361:
362: // ************************************************************************
363: // inner class
364: // ************************************************************************
365:
366: /**
367: * This is a helper class to model a Key of an Object
368: */
369: private static final class Key {
370: final ValueContainer[] m_containers;
371:
372: Key(final ValueContainer[] containers) {
373: m_containers = new ValueContainer[containers.length];
374:
375: for (int i = 0; i < containers.length; i++) {
376: Object value = containers[i].getValue();
377: JdbcType type = containers[i].getJdbcType();
378:
379: // BRJ:
380: // convert all Numbers to Long to simplify equals
381: // Long(100) is not equal to Integer(100)
382: //
383: // could lead to problems when Floats are used as key
384: // converting to String could be a better alternative
385: if (value instanceof Number) {
386: value = new Long(((Number) value).longValue());
387: }
388:
389: m_containers[i] = new ValueContainer(value, type);
390: }
391: }
392:
393: public boolean equals(Object other) {
394: if (other == this ) {
395: return true;
396: }
397: if (!(other instanceof Key)) {
398: return false;
399: }
400:
401: Key otherKey = (Key) other;
402: EqualsBuilder eb = new EqualsBuilder();
403:
404: eb.append(m_containers, otherKey.m_containers);
405: return eb.isEquals();
406: }
407:
408: public int hashCode() {
409: HashCodeBuilder hb = new HashCodeBuilder();
410: hb.append(m_containers);
411:
412: return hb.toHashCode();
413: }
414: }
415:
416: // ************************************************************************
417: // inner class
418: // ************************************************************************
419: private static final class GenericObject {
420: private String tablename;
421: private String[] columnNames;
422: private ValueContainer[] values;
423:
424: public GenericObject(String tablename, String[] columnNames,
425: ValueContainer[] values) {
426: this .tablename = tablename;
427: this .columnNames = columnNames;
428: this .values = values;
429: if (values != null && columnNames.length != values.length) {
430: throw new OJBRuntimeException(
431: "Column name array and value array have NOT same length");
432: }
433: }
434:
435: public boolean equals(Object obj) {
436: if (this == obj) {
437: return true;
438: }
439: boolean result = false;
440: if (obj instanceof GenericObject) {
441: GenericObject other = (GenericObject) obj;
442: result = (tablename.equalsIgnoreCase(other.tablename)
443: && (columnNames != null)
444: && (other.columnNames != null) && (columnNames.length == other.columnNames.length));
445:
446: if (result) {
447: for (int i = 0; i < columnNames.length; i++) {
448: int otherIndex = other
449: .indexForColumn(columnNames[i]);
450: if (otherIndex < 0) {
451: result = false;
452: break;
453: }
454: result = values[i]
455: .equals(other.values[otherIndex]);
456: if (!result)
457: break;
458: }
459: }
460: }
461: return result;
462: }
463:
464: int indexForColumn(String name) {
465: int result = -1;
466: for (int i = 0; i < columnNames.length; i++) {
467: if (columnNames[i].equals(name)) {
468: result = i;
469: break;
470: }
471: }
472: return result;
473: }
474:
475: public int hashCode() {
476: return super .hashCode();
477: }
478:
479: public ValueContainer getValueFor(String columnName) {
480: try {
481: return values[indexForColumn(columnName)];
482: } catch (Exception e) {
483: throw new OJBRuntimeException(
484: "Can't find value for column "
485: + columnName
486: + (indexForColumn(columnName) < 0 ? ". Column name was not found"
487: : ""), e);
488: }
489: }
490:
491: public String getTablename() {
492: return tablename;
493: }
494:
495: public String[] getColumnNames() {
496: return columnNames;
497: }
498:
499: public ValueContainer[] getValues() {
500: return values;
501: }
502:
503: public void setValues(ValueContainer[] values) {
504: this .values = values;
505: }
506:
507: public String toString() {
508: return new ToStringBuilder(this ).append("tableName",
509: tablename).append("columnNames", columnNames)
510: .append("values", values).toString();
511: }
512: }
513: }
|