/* * BusinessObjectCollection * * Copyright (C) 2010 Jaroslav Merxbauer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package notwa.wom; import java.util.ArrayList; import notwa.dal.SmartResultSet; import notwa.exception.ContextException; import notwa.exception.DeveloperException; /** * Abstract class providing a general behavior connected with maintaining a * collection of <code>BusinessObject</code>s. * <p>This collection is usualy build upon the data queried from the database.</p> * * * @author Tomas Studnicka * @author Jaroslav Merxbauer * @param <T> The concrete implemntation of <code>BusinessObject</code> to be hold * by this collection. */ public abstract class BusinessObjectCollection<T extends BusinessObject> extends ArrayList<T> { /** * The current <code>Context</code> we are living within. This <code>Context</code> * keeps all the <code>BusinessObjects</code> related to our literal context. */ protected Context currentContext; /** * The <code>ResultSet</code> we are originated from, if there is one. This * <code>RecordSet</code> could than be utilized during the databese update * process. */ protected SmartResultSet resultSet; /** * The flag indicating that this collection is "closed" which actually means * that all changes made from this point should be tracked and properly updated * to the data source. */ protected boolean closed; /** * This flag turns to true as soon as any change is made to the closed collection. */ protected boolean updateRequired; /** * The sole basic constructor providing a potential of setting the current * <code>Context</code> and the initial <code>ResultSet</code> where this * collection have came from. * * @param currentContext The actual <code>Context</code> where this * <code>BusinessObjectCollection</code> lives. * @param resultSet The original <code>ResultSet</code> based which this * <code>BusinessObjectCollection</code> has been created. */ public BusinessObjectCollection(Context currentContext, SmartResultSet resultSet) { this.currentContext = currentContext; this.resultSet = resultSet; } /** * Attempts to add the given <code>BusinessObject</code> to this * <code>BusinessObjectCollection</code>. It is required that: * <ul> * <li> Both <code>BusinessObject</code> and <code>BusinessObjectCollection</code> * are <code>Context</code> aware.</li> * <li> The given <code>BusinessObject</code> isn't already present in the * <code>BusinessObjectCollection</code>.</li> * </ul> * If this <code>BusinessObjectCollection</code> has been already closed, * which means that we are not building this <code>BusinessObjectCollection</code> * but rather updating, we will mark this collection to require update and * sets the <code>inserted</code> flag of <code>BusinessObject</code> to <code>true</code>. * * @param bo <code>BusinessObject</code> to be added to this <code>BusinessObjectCollection</code> * @return <code>true</code> if the addition success, <code>false</code> otherwise */ @Override public boolean add(T bo) { /* * Make sure that we are context aware and both BusinessObject and * BusinessObjectCollection lives in the same context! */ if ((bo.getCurrentContext() == null) || (this.getCurrentContext() == null) || !bo.getCurrentContext().equals(this.getCurrentContext())) { throw new RuntimeException("Context awareness violated!", new ContextException("BusinessObject lives in another context than BusinessObjectCollection!")); } /* * If the given BO is id-less, acquire correct temporary index for it */ if (!bo.hasUniqeIdentifier()) { acquireUniqeIdentifier(bo); } /* * Make sure that the same BusinessObject isn't already present in the * collection. */ if (super.contains(bo)) { return false; } /* * Try to add the BusinesObject to the BusinessObjectCollection. */ if (!super.add(bo)) { return false; } /* * Attach BusinessObject with this BusinessObjectCollection togeather */ bo.attach(this); /* * If this collection is already closed, which means that we cannot freely * add/remove BusinessObjects from it, perform appropriate actions to * remark that this new item needs to be inserted to the database during * a database update */ if (isClosed()) { bo.setInserted(true); setUpdateRequired(true); } /* * If we have reached this point we have been able to add the BusinessObject * to the BusinessObjectCollection */ return true; } /** * Marks the given <code>BusinessObject</code> for deletion from this * <code>BusinessObjectCollection</code> if this is already closed <code>BusinessObjectCollection</code>, * oterwise it will delete it physicaly from this <code>BusinessObjectCollection</code>. * <p>It is required that: * <ul> * <li> Both <code>BusinessObject</code> and <code>BusinessObjectCollection</code> * are <code>Context</code> aware.</li> * </ul> * </p> * * @param bo <code>BusinessObject</code> to be mark for removal from this * <code>BusinessObjectCollection</code> * @return <code>true</code> if the mark success, <code>false</code> otherwise */ public boolean remove(T bo) { /* * Make sure that we are context aware and both BusinessObject and * BusinessObjectCollection lives in the same context! */ if ((bo.getCurrentContext() == null) || (this.getCurrentContext() == null) || !bo.getCurrentContext().equals(this.getCurrentContext())) { throw new RuntimeException("Context awareness violated!", new ContextException("BusinessObject lives in another context than BusinessObjectCollection!")); } /* * Make sure that the BusinessObject is present in the collection. */ if (!super.contains(bo)) { return false; } /* * If this collection is already closed and the BusinessObject hasn't * been previously inserted to the collection (so there is no need for * a database update, mark the item as deleted and make sure that this * collection is aware of that change to be updated to the database. * Otherwise, just remove the BusinessObject from this collection * and detach it. */ if (isClosed() && !bo.isInserted()) { bo.setDeleted(true); setUpdateRequired(true); } else { super.remove(bo); bo.detach(); } /* * If we have reached this point, we can leave with true. */ return true; } /** * Gets the element contained in the <code>BusinessObjectCollection</code> on * the given index. * * @param index Index where to pick-up the <code>BusinessObject</code>. * @return The <code>BusinessObject</code> present on the position represented * by the given index. */ @Override public T get(int index) { return (T) super.get(index); } /** * Shake away all <code>BusinessObject</code>s attached to this * <code>BusinessObjectCollection</code>. This factically means: * <ol> * <li>Detach all <code>BusinessObject</code>s from colletion</li> * <li>Clear this collection</li> * <li>Clear the current context</li> * </ol> */ public void shakeAllAway() { for (BusinessObject bo : this) { bo.detach(); } super.clear(); currentContext.clearWorkItems(); } /** * Gets the <code>Context</code> where this collection currently lives. * * @return The current <code>Context</code> */ public Context getCurrentContext() { return currentContext; } /** * Sets the <code>Context</code> where this collection is going to live. * * @param currentContext The context to live in. */ public void setCurrentContext(Context currentContext) { this.currentContext = currentContext; } /** * Removes all <code>BusinessObject</code> marked for deletion from this * <code>BusinessObjectCollection</code> and commits all changes for the others. */ public void commit() { ArrayList<BusinessObject> garbage = new ArrayList<BusinessObject>(); for (BusinessObject bo : this) { if (bo.isDeleted()) { garbage.add(bo); } else { bo.commit(); } } super.removeAll(garbage); setUpdateRequired(false); } /** * Rollbacks all changes made to this <code>BusinessObjectCollection</code>. */ public void rollback() { for (BusinessObject bo : this) { bo.rollback(); } setUpdateRequired(false); } /** * Sets this <code>BusinesObjectCollection</code> as closed which actually means * that the current version is a mirror image of the data queried from the database. * <p>This version is then changed over the time: * <ul> * <li>Data are modified - database update</li> * <li>Data are inserted - database insert</li> * <li>Data are deleted - database delete</li> * </ul> * <code>BusinessObjectCollection</code> is aware of such a changes while it is * being closed and makes sure that all <code>BusinessObject</code>s are aware * of their current status (deleted/inserted) or the <code>BusinessObject</code> * may query this status using <code>isClosed</code> and could remark that it * is changed.</p> * <p>And as soon as the user performs an action which forces the data to be * saved to the database, this <code>BusinessObjectCollection</code> could take * appropriate steps to update the database appropriately.</p> * * @param closed <code>true</code> if this <code>BusinessObjectCollection</code> * should have been closed, <code>false</code> otherwise. */ public void setClosed(boolean closed) { this.closed = closed; } /** * Checks whether this <code>BusinesObjectCollection</code> is closed which actually means * that the current version is a mirror image of the data queried from the database. * <p>This version is then changed over the time: * <ul> * <li>Data are modified - database update</li> * <li>Data are inserted - database insert</li> * <li>Data are deleted - database delete</li> * </ul> * <code>BusinessObjectCollection</code> is aware of such a changes while it is * being closed and makes sure that all <code>BusinessObject</code>s are aware * of their current status (deleted/inserted) or the <code>BusinessObject</code> * may query this status using this method and could remark that it is changed.</p> * <p>And as soon as the user performs an action which forces the data to be * saved to the database, this <code>BusinessObjectCollection</code> could take * appropriate steps to update the database appropriately.</p> * * @return <code>true</code> if this <code>BusinessObjectCollection</code> is * already closed, <code>false</code> otherwise. */ public boolean isClosed() { return closed; } /** * Checks whether this <code>BusinessObjectCollection</code> have been updated * since it was marked as closed. * @see #isClosed() * * @return <code>true</code> if this <code>BusinessObjectCollection</code> differs * from its database representation, <code>false</code> otherwise. */ public boolean isUpdateRequired() { return updateRequired; } /** * Sets whether this <code>BusinessObjectCollection</code> have been updated * since it was marked as closed. * @see #isClosed() * * @param updateRequired <code>true</code> if this <code>BusinessObjectCollection</code> * differs from its database representation, * <code>false</code> otherwise. */ public void setUpdateRequired(boolean updateRequired) { this.updateRequired = updateRequired; } /** * Sets the result set upon which this collection is build and through which * the database can be easily updated. * * @param resultSet The original <code>ResultSet</code> based which this * <code>BusinessObjectCollection</code> has been created. */ public void setResultSet(SmartResultSet resultSet) { this.resultSet = resultSet; } /** * Gets the result set upon which this collection is build and through which * the database can be easily updated. * * @return resultSet The original <code>ResultSet</code> based which this * <code>BusinessObjectCollection</code> has been created. */ public SmartResultSet getResultSet() { return resultSet; } /** * Notifies this <code>BusinessObjectCollection</code> that its owned * <code>BusinessObject</code> has been updated. * This results in marking this collection as well as the object to be updated. * * @param businessObject The actuall <code>BusinessObject</code> that has * been updated. */ public void setUpdated(T businessObject) { setUpdateRequired(true); businessObject.setUpdated(true); } /** * Gets the concrete implementation of <code>BusinessObject</code> by its * primary key. * <p>The concrete implementation should know how to handle the given primary * key</p> * @param primaryKey The actuall primary key which should be used to the * <code>BusinessObject</code> lookup. * @return The found concrete implementation of the <code>BusinessObject</code>, * <code>null</code> if there is none such. * @throws DeveloperException If the developer haven't precisely specified the * comparing methods for the concrete implementation * of <code>BusinessObject</code>. */ public abstract T getByPrimaryKey(Object primaryKey) throws DeveloperException; /** * Provides the given concrete implementation of <code>BusinessObject</code> * with valid temporary uniqe identifiyer. * This is going to be replaced with valid one after the database update. * * @param bo Concrete implementation of <code>BusinessObject</code> to be * provided with uniqe identifier. */ protected abstract void acquireUniqeIdentifier(T bo); }