001: /**
002: * Copyright (C) 2007 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import java.util.*;
020: import hu.netmind.persistence.parser.*;
021: import org.apache.log4j.Logger;
022:
023: /**
024: * This is a generic container handler. It manages the ContainerImpl interface.
025: * @author Brautigam Robert
026: * @version Revision: $Revision$
027: */
028: public abstract class ContainerHandler extends AbstractTypeHandler {
029: private static Logger logger = Logger
030: .getLogger(ContainerHandler.class);
031:
032: public ContainerHandler(StoreContext context) {
033: super (context);
034: }
035:
036: /**
037: * Get the attribute types that are representing this type
038: * in the original object.
039: */
040: public Map getAttributeTypes(String attributeName) {
041: // Return the attributes in the original object.
042: HashMap attributeTypes = new HashMap();
043: attributeTypes.put(attributeName, Long.class);
044: attributeTypes.put(attributeName + "_itemclass", String.class);
045: return attributeTypes;
046: }
047:
048: /**
049: * Save the collection.
050: */
051: public Object save(ClassInfo classInfo, Object current,
052: String attributeName, Transaction transaction,
053: Long currentSerial, Object oldValue, Object newValue,
054: Set waitingObjects, List events, Map changedAttributes,
055: Map dbAttributes) {
056: // Determine what to do with the null-indicator inside the
057: // parent object. Keep in mind, that something changed!
058: if (oldValue == null) {
059: // If old list is null, we assume that
060: // the new list is not null, so insert current serial as last
061: // to indicate the list will be non-null, and to remember version.
062: // If the new list is also null, the next condition will
063: // catch it.
064: changedAttributes.put(attributeName, currentSerial);
065: }
066: if (newValue == null) {
067: // If list is null, we assume that
068: // previously it was non-null so insert 'null'
069: // to indicate it should be null.
070: changedAttributes.put(attributeName, null);
071: changedAttributes.put(attributeName + "_itemclass", null);
072: }
073: // Save collection
074: Long lastSerial = (Long) dbAttributes.get(attributeName);
075: String itemClassName = (String) dbAttributes.get(attributeName
076: + "_itemclass");
077: if (newValue == null) {
078: logger.debug("saving container, new value is null.");
079: // If new value is null, then set the attribute to null
080: // on tracker, and clear all current values.
081: if (oldValue != null) {
082: // Clean all values, just to be sure.
083: ((Container) oldValue).clear();
084: ((Container) oldValue).save(transaction, currentSerial,
085: waitingObjects, events);
086: }
087: } else if ((newValue == oldValue)
088: && (((Container) newValue).getLastSerial()
089: .equals(lastSerial))) {
090: logger.debug("saving container, value is current.");
091: // If the value is the original value, and it is current,
092: // then simply save. Attribute stays the same.
093: ((Container) newValue).save(transaction, currentSerial,
094: waitingObjects, events);
095: changedAttributes.put(attributeName, currentSerial);
096: changedAttributes.put(attributeName + "_itemclass",
097: ((Container) newValue).getItemClassName());
098: } else {
099: Container currentList = (Container) oldValue;
100: if ((currentList == null)
101: || (!currentList.getLastSerial().equals(lastSerial))) {
102: // This means old list is not current, so we must load
103: // the current list. The current list is an empty list,
104: // even if the real current list would be null.
105: Map pseudoAttributes = new HashMap();
106: pseudoAttributes.put(attributeName, currentSerial);
107: pseudoAttributes.put(attributeName + "_itemclass",
108: dbAttributes.get(attributeName + "_itemclass"));
109: TimeControl currentListTimeControl = new TimeControl(
110: currentSerial, transaction.getSerial(), true);
111: currentList = (Container) unmarshallType(classInfo,
112: current, attributeName, pseudoAttributes,
113: currentListTimeControl);
114: if (logger.isDebugEnabled())
115: logger
116: .debug("saving container, selected current list: "
117: + currentList.size()
118: + ", tracked value time control: "
119: + currentListTimeControl);
120: } else {
121: if (logger.isDebugEnabled())
122: logger
123: .debug("saving container, current list is ok, serial is: "
124: + lastSerial);
125: }
126: // Diff to the current list, and save the changes.
127: logger.debug("diffing containers (clean)...");
128: currentList.clear(); // Delete entries
129: logger.debug("diffing containers (add)...");
130: currentList.addAll(newValue); // Add new entries
131: // Save
132: logger.debug("diffing containers (save)...");
133: ((Container) currentList).save(transaction, currentSerial,
134: waitingObjects, events);
135: // If all went well, update attribute. It is important, that
136: // this list will not be referenced prior to the completion of
137: // save operation, because inserted item may not yet exist at
138: // this point.
139: changedAttributes.put(attributeName, currentSerial);
140: changedAttributes.put(attributeName + "_itemclass",
141: ((Container) currentList).getItemClassName());
142: logger
143: .debug("diffing containers finished, final container item class is: "
144: + ((Container) currentList)
145: .getItemClassName());
146: newValue = currentList;
147: }
148: return newValue;
149: }
150:
151: public abstract Class getContainerClass();
152:
153: /**
154: * Return a new instance of Container.
155: */
156: public Object unmarshallType(ClassInfo classInfo, Object obj,
157: String attributeName, Map marshalledValues,
158: TimeControl timeControl) {
159: // First, if the attribute is false, then return null!
160: Long lastSerial = (Long) marshalledValues.get(attributeName);
161: String itemClassName = (String) marshalledValues
162: .get(attributeName + "_itemclass");
163: if (lastSerial == null)
164: return null;
165: // Return with implementation
166: try {
167: Container impl = (Container) getContainerClass()
168: .newInstance();
169: impl.init(context, classInfo, obj, attributeName,
170: itemClassName, lastSerial, timeControl);
171: return impl;
172: } catch (Throwable e) {
173: throw new StoreException(
174: "could not instantiate collection implementation: "
175: + getContainerClass(), e);
176: }
177: }
178:
179: /**
180: * Determine whether the two values differ.
181: */
182: public boolean hasChanged(ClassInfo info, Object obj,
183: String attributeName, Map dbAttributes) {
184: logger.debug("determining whether container attribute: "
185: + attributeName + " changed.");
186: // First get old and new values, and serial
187: Container oldValue = (Container) context.getObjectTracker()
188: .getAttributeValue(obj, attributeName);
189: Object newValue = info.getAttributeValue(obj, attributeName);
190: Long lastSerial = (Long) dbAttributes.get(attributeName);
191: // If the new value is a Container, we can use a lot of extra
192: // information, so treat it differently.
193: if (newValue instanceof Container) {
194: // Not changed if:
195: // - new value is the same instance as old one
196: // - value is current (same as in db)
197: // - has not changed internally
198: boolean result = !((newValue == oldValue)
199: && (oldValue.getLastSerial().equals(lastSerial)) && (!oldValue
200: .hasChanged()));
201: logger.debug("new value is also container, changed: "
202: + result);
203: return result;
204: } else {
205: // Handle nulls
206: if (oldValue == null) {
207: logger.debug("old value was null, new value is null: "
208: + (newValue == null));
209: return newValue != null;
210: }
211: // Value is not a list impl, so compare it to old one, if that
212: // is current.
213: if (oldValue.getLastSerial().equals(lastSerial)) {
214: // Old value is current, so we can compare to that.
215: boolean result = !oldValue.equals(newValue);
216: logger
217: .debug("new value is not a container impl, old value was current, changed: "
218: + result);
219: return result;
220: } else {
221: // Old value is not current, so load current list and
222: // compare.
223: Transaction tx = context.getTransactionTracker()
224: .getTransaction(TransactionTracker.TX_OPTIONAL);
225: Long txSerial = null;
226: if (tx != null)
227: txSerial = tx.getSerial();
228: TimeControl currentControl = new TimeControl(
229: lastSerial, txSerial, txSerial == null ? false
230: : true);
231: Container currentImpl = (Container) unmarshallType(
232: info, obj, attributeName, dbAttributes,
233: currentControl);
234: boolean result = !currentImpl.equals(newValue);
235: logger
236: .debug("new value is not a container impl, old value was not current, changed: "
237: + result);
238: return result;
239: }
240: }
241: }
242:
243: public void postSave(Object value) {
244: if (value != null)
245: ((Container) value).reload();
246: }
247: }
|