001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.bridge.implementation;
012:
013: import java.util.*;
014: import java.io.*;
015: import org.mmbase.bridge.*;
016: import org.mmbase.security.UserContext;
017: import org.mmbase.module.core.*;
018: import org.mmbase.util.logging.*;
019:
020: /**
021: * The basic implementation for a Transaction cLoud.
022: * A Transaction cloud is a cloud which buffers allc hanegs made to nodes -
023: * which means that chanegs are committed only if you commit the transaction itself.
024: * This mechanism allows you to rollback changes if something goes wrong.
025: * @author Pierre van Rooden
026: * @version $Id: BasicTransaction.java,v 1.36 2008/01/09 10:56:36 michiel Exp $
027: */
028: public class BasicTransaction extends BasicCloud implements Transaction {
029:
030: private static final Logger log = Logging
031: .getLoggerInstance(BasicTransaction.class);
032: /**
033: * The id of the transaction for use with the transaction manager.
034: */
035: protected String transactionContext; // not final because of deserialization
036:
037: private boolean canceled = false;
038: private boolean committed = false;
039: /**
040: * The name of the transaction as used by the user.
041: */
042: protected String transactionName; // not final because of deserialization
043:
044: protected BasicCloud parentCloud; // not final because of deserialization
045:
046: /**
047: * @since MMBase 1.9
048: */
049: protected final Collection<MMObjectNode> getCoreNodes() {
050: // if the parent cloud is itself a transaction,
051: // do not create a new one, just use that context instead!
052: // this allows for nesting of transactions without loosing performance
053: // due to additional administration
054: if (parentCloud instanceof BasicTransaction) {
055: return ((BasicTransaction) parentCloud).getCoreNodes();
056: } else {
057: try {
058: // XXX: the current transaction manager does not allow multiple transactions with the
059: // same name for different users
060: // We solved this here, but this should really be handled in the Transactionmanager.
061: log.debug("using transaction " + transactionContext);
062: Collection<MMObjectNode> cn = BasicCloudContext.transactionManager
063: .getTransactions().get(transactionContext);
064: if (cn == null) {
065: cn = BasicCloudContext.transactionManager
066: .createTransaction(transactionContext);
067: }
068: return cn;
069: } catch (TransactionManagerException e) {
070: throw new BridgeException(e.getMessage(), e);
071: }
072: }
073: }
074:
075: /*
076: * Constructor to call from the CloudContext class.
077: * Package only, so cannot be reached from a script.
078: * @param transactionName name of the transaction (assigned by the user)
079: * @param cloud The cloud this transaction is working on
080: */
081: BasicTransaction(String transactionName, BasicCloud cloud) {
082: super (transactionName, cloud);
083: this .transactionName = transactionName;
084: this .parentCloud = cloud;
085: // if the parent cloud is itself a transaction,
086: // do not create a new one, just use that context instead!
087: // this allows for nesting of transactions without loosing performance
088: // due to additional administration
089: if (parentCloud instanceof BasicTransaction) {
090: transactionContext = ((BasicTransaction) parentCloud).transactionContext;
091: } else {
092: // XXX: the current transaction manager does not allow multiple transactions with the
093: // same name for different users
094: // We solved this here, but this should really be handled in the Transactionmanager.
095: transactionContext = account + "_" + transactionName;
096: log.debug("using transaction " + transactionContext);
097: getCoreNodes(); // will call 'createTransaction
098: }
099: }
100:
101: public NodeList getNodes() {
102: return new BasicNodeList(getCoreNodes(), this );
103: }
104:
105: public synchronized boolean commit() {
106: if (canceled) {
107: throw new BridgeException("Cannot commit transaction'"
108: + name + "' (" + transactionContext
109: + "), it was already canceled.");
110: }
111: if (committed) {
112: throw new BridgeException("Cannot commit transaction'"
113: + name + "' (" + transactionContext
114: + "), it was already committed.");
115: }
116: log.debug("Committing transaction " + transactionContext);
117:
118: parentCloud.transactions.remove(transactionName); // hmpf
119:
120: // if this is a transaction within a transaction (theoretically possible)
121: // leave the committing to the 'parent' transaction
122: if (parentCloud instanceof Transaction) {
123: // do nothing
124: } else {
125: try {
126: assert BasicCloudContext.transactionManager
127: .getTransaction(transactionContext).size() == getNodes()
128: .size();
129:
130: log.info("Commiting " + getNodes());
131: BasicCloudContext.transactionManager
132: .resolve(transactionContext);
133: BasicCloudContext.transactionManager.commit(
134: userContext, transactionContext);
135:
136: // This is a hack to call the commitprocessors which are only available in the bridge.
137: for (Node n : getNodes()) {
138: log.debug("Commiting " + n);
139: if (n == null) {
140: log.warn("Found null in transaction");
141: continue;
142: }
143: if (!n.isChanged() && !n.isNew()) {
144: log.debug("Ignored because not changed "
145: + n.isChanged() + "/" + n.isNew());
146: continue;
147: }
148: if (TransactionManager.EXISTS_NOLONGER.equals(n
149: .getStringValue("_exists"))) {
150: log.debug("Ignored because exists no longer.");
151: continue;
152: }
153: log.debug("Calling commit on " + n);
154: n.commit();
155: }
156:
157: } catch (TransactionManagerException e) {
158: // do we drop the transaction here or delete the trans context?
159: // return false;
160: throw new BridgeException(e.getMessage()
161: + " for transaction with " + getNodes(), e);
162: }
163: }
164:
165: committed = true;
166: return true;
167: }
168:
169: public synchronized void cancel() {
170: if (canceled) {
171: throw new BridgeException("Cannot cancel transaction'"
172: + name + "' (" + transactionContext
173: + "), it was already canceled.");
174: }
175: if (committed) {
176: throw new BridgeException("Cannot cancel transaction'"
177: + name + "' (" + transactionContext
178: + "), it was already committed.");
179: }
180:
181: // if this is a transaction within a transaction (theoretically possible)
182: // call the 'parent' transaction to cancel everything
183: if (parentCloud instanceof Transaction) {
184: ((Transaction) parentCloud).cancel();
185: } else {
186: try {
187: // BasicCloudContext.transactionManager.cancel(account, transactionContext);
188: BasicCloudContext.transactionManager.cancel(
189: userContext, transactionContext);
190: } catch (TransactionManagerException e) {
191: // do we drop the transaction here or delete the trans context?
192: throw new BridgeException(e.getMessage(), e);
193: }
194: }
195: // remove the transaction from the parent cloud
196: parentCloud.transactions.remove(transactionName);
197: canceled = true;
198: }
199:
200: /*
201: * Transaction-notification: add a new temporary node to a transaction.
202: * @param currentObjectContext the context of the object to add
203: */
204: @Override
205: void add(String currentObjectContext) {
206: try {
207: BasicCloudContext.transactionManager.addNode(
208: transactionContext, account, currentObjectContext);
209: } catch (TransactionManagerException e) {
210: throw new BridgeException(e.getMessage(), e);
211: }
212: }
213:
214: /*
215: */
216: @Override
217: int add(BasicNode node) {
218: int id = node.getNumber();
219: String currentObjectContext = BasicCloudContext.tmpObjectManager
220: .getObject(account, "" + id, "" + id);
221: // store new temporary node in transaction
222: add(currentObjectContext);
223: node.setNode(BasicCloudContext.tmpObjectManager.getNode(
224: account, "" + id));
225: // check nodetype afterwards?
226: return id;
227: }
228:
229: @Override
230: void createAlias(BasicNode node, String aliasName) {
231: checkAlias(aliasName);
232: try {
233: String aliasContext = BasicCloudContext.tmpObjectManager
234: .createTmpAlias(aliasName, account, "a"
235: + node.temporaryNodeId, ""
236: + node.temporaryNodeId);
237: BasicCloudContext.transactionManager.addNode(
238: transactionContext, account, aliasContext);
239: } catch (TransactionManagerException e) {
240: throw new BridgeException(e.getMessage(), e);
241: }
242: }
243:
244: /*
245: * Transaction-notification: remove a temporary (not yet committed) node in a transaction.
246: * @param currentObjectContext the context of the object to remove
247: */
248: @Override
249: void remove(String currentObjectContext) {
250: try {
251: BasicCloudContext.transactionManager.removeNode(
252: transactionContext, account, currentObjectContext);
253: } catch (TransactionManagerException e) {
254: throw new BridgeException(e.getMessage(), e);
255: }
256: }
257:
258: @Override
259: void remove(MMObjectNode node) {
260: String oMmbaseId = "" + node.getValue("number");
261: String currentObjectContext = BasicCloudContext.tmpObjectManager
262: .getObject(account, "" + oMmbaseId, oMmbaseId);
263: add(currentObjectContext);
264: delete(currentObjectContext);
265: }
266:
267: void delete(String currentObjectContext, MMObjectNode node) {
268: delete(currentObjectContext);
269: }
270:
271: /*
272: * Transaction-notification: remove an existing node in a transaction.
273: * @param currentObjectContext the context of the object to remove
274: */
275: void delete(String currentObjectContext) {
276: try {
277: BasicCloudContext.transactionManager.deleteObject(
278: transactionContext, account, currentObjectContext);
279: } catch (TransactionManagerException e) {
280: throw new BridgeException(e.getMessage(), e);
281: }
282: }
283:
284: @Override
285: boolean contains(MMObjectNode node) {
286: // additional check, so transaction can still get nodes after it has committed.
287: if (transactionContext == null) {
288: return false;
289: }
290: try {
291: Collection<MMObjectNode> transaction = BasicCloudContext.transactionManager
292: .getTransaction(transactionContext);
293: return transaction.contains(node);
294: } catch (TransactionManagerException tme) {
295: throw new BridgeException(tme.getMessage(), tme);
296: }
297: }
298:
299: @Override
300: BasicNode makeNode(MMObjectNode node, String nodeNumber) {
301: if (committed) {
302: return parentCloud.makeNode(node, nodeNumber);
303: } else {
304: return super .makeNode(node, nodeNumber);
305: }
306: }
307:
308: /**
309: * If this Transaction is scheduled to be garbage collected, the transaction is canceled and cleaned up.
310: * Unless it has already been committed/canceled, ofcourse, and
311: * unless the parentcloud of a transaction is a transaction itself.
312: * In that case, the parent transaction should cancel!
313: * This means that a transaction is always cleared - if it 'times out', or is not properly removed, it will
314: * eventually be removed from the MMBase cache.
315: */
316: @Override
317: protected void finalize() {
318: if ((transactionContext != null)
319: && !(parentCloud instanceof Transaction)) {
320: cancel();
321: }
322: }
323:
324: public boolean isCanceled() {
325: return canceled;
326: }
327:
328: public boolean isCommitted() {
329: return committed;
330: }
331:
332: public Object getProperty(Object key) {
333: Object value = super .getProperty(key);
334: if (value == null) {
335: return parentCloud.getProperty(key);
336: } else {
337: return value;
338: }
339: }
340:
341: /**
342: * @see org.mmbase.bridge.Transaction#getCloudName()
343: */
344: public String getCloudName() {
345: if (parentCloud instanceof Transaction) {
346: return ((Transaction) parentCloud).getCloudName();
347: } else {
348: return parentCloud.getName();
349: }
350: }
351:
352: private void readObject(ObjectInputStream in) throws IOException,
353: ClassNotFoundException {
354: _readObject(in);
355: transactionContext = (String) in.readObject();
356: canceled = in.readBoolean();
357: committed = in.readBoolean();
358: transactionName = (String) in.readObject();
359: parentCloud = (BasicCloud) in.readObject();
360: }
361:
362: private void writeObject(ObjectOutputStream out) throws IOException {
363: _writeObject(out);
364: out.writeObject(transactionContext);
365: out.writeBoolean(canceled);
366: out.writeBoolean(committed);
367: out.writeObject(transactionName);
368: out.writeObject(parentCloud);
369: }
370:
371: public String toString() {
372: UserContext uc = getUser();
373: return "BasicTransaction " + count + "'" + getName() + "' of "
374: + (uc != null ? uc.getIdentifier() : "NO USER YET")
375: + " @" + Integer.toHexString(hashCode());
376: }
377: }
|