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: package org.mmbase.module.core;
011:
012: import java.util.*;
013: import org.mmbase.module.corebuilders.*;
014:
015: import org.mmbase.util.logging.Logger;
016: import org.mmbase.util.logging.Logging;
017: import org.mmbase.security.*;
018:
019: /**
020: * The MMBase transaction manager manages a group of changes.
021: * @javadoc
022: *
023: * @author Rico Jansen
024: * @version $Id: TransactionManager.java,v 1.42 2008/01/09 12:26:51 michiel Exp $
025: */
026: public class TransactionManager {
027:
028: private static final Logger log = Logging
029: .getLoggerInstance(TransactionManager.class);
030:
031: static final String EXISTS_NO = "no";
032: static final int I_EXISTS_NO = 0;
033: static final String EXISTS_YES = "yes";
034: static final int I_EXISTS_YES = 1;
035: public static final String EXISTS_NOLONGER = "nolonger";
036: static final int I_EXISTS_NOLONGER = 2;
037:
038: private TemporaryNodeManager tmpNodeManager;
039: private TransactionResolver transactionResolver;
040:
041: protected Map<String, Collection<MMObjectNode>> transactions = new HashMap<String, Collection<MMObjectNode>>();
042:
043: public static TransactionManager instance;
044:
045: /**
046: * @since MMBase-1.9
047: */
048: public static TransactionManager getInstance() {
049: if (instance == null) {
050: instance = new TransactionManager();
051: }
052: return instance;
053: }
054:
055: private TransactionManager() {
056: }
057:
058: public TemporaryNodeManager getTemporaryNodeManager() {
059: if (tmpNodeManager == null) {
060: tmpNodeManager = new TemporaryNodeManager(MMBase
061: .getMMBase());
062: }
063: return tmpNodeManager;
064:
065: }
066:
067: private TransactionResolver getTransactionResolver() {
068: if (transactionResolver == null) {
069: transactionResolver = new TransactionResolver(MMBase
070: .getMMBase());
071: }
072: return transactionResolver;
073: }
074:
075: /**
076: * Returns transaction with given name.
077: * @param transactionName The name of the transaction to return
078: * @exception TransactionManagerExcpeption if the transaction with given name does not exist
079: * @return Collection containing the nodes in this transaction (as {@link org.mmbase.module.core.MMObjectNode}s).
080: */
081: synchronized public Collection<MMObjectNode> getTransaction(
082: String transactionName) throws TransactionManagerException {
083: Collection<MMObjectNode> transaction = transactions
084: .get(transactionName);
085: if (transaction == null) {
086: throw new TransactionManagerException("Transaction "
087: + transactionName
088: + " does not exist (existing are "
089: + transactions.keySet() + ")");
090: } else {
091: return transaction;
092: }
093: }
094:
095: /**
096: * Return a an unmodifable Map with all transactions. This map can be used to explore the
097: * existing transactions.
098: *
099: * @since MMBase-1.9
100: */
101: public Map<String, Collection<MMObjectNode>> getTransactions() {
102: return Collections.unmodifiableMap(transactions);
103: }
104:
105: /**
106: * Creates transaction with given name.
107: * @param transactionName The name of the transaction to return
108: * @exception TransactionManagerExcpeption if the transaction with given name existed already
109: * @return Collection containing the nodes in this transaction (so, this is an empty collection)
110: */
111: synchronized public Collection<MMObjectNode> createTransaction(
112: String transactionName) throws TransactionManagerException {
113: if (!transactions.containsKey(transactionName)) {
114: List<MMObjectNode> transactionNodes = new ArrayList<MMObjectNode>();
115: transactions.put(transactionName, transactionNodes);
116: return transactionNodes;
117: } else {
118: throw new TransactionManagerException("Transaction "
119: + transactionName + " already exists");
120: }
121: }
122:
123: /**
124: * Removes the transaction with given name
125: * @return the collection with nodes from the removed transaction or <code>null</code> if no transaction with this name existed
126: */
127: synchronized protected Collection<MMObjectNode> deleteTransaction(
128: String transactionName) {
129: return transactions.remove(transactionName);
130: }
131:
132: public String addNode(String transactionName, String owner,
133: String tmpnumber) throws TransactionManagerException {
134: Collection<MMObjectNode> transaction = getTransaction(transactionName);
135: MMObjectNode node = getTemporaryNodeManager().getNode(owner,
136: tmpnumber);
137: if (node != null) {
138: if (!transaction.contains(node)) {
139: transaction.add(node);
140: // } else {
141: // throw new TransactionManagerException(
142: // "Node " + tmpnumber + " not added as it was already in transaction " + transactionName);
143: }
144: } else {
145: throw new TransactionManagerException("Node " + tmpnumber
146: + " doesn't exist.");
147: }
148: return tmpnumber;
149: }
150:
151: public String removeNode(String transactionName, String owner,
152: String tmpnumber) throws TransactionManagerException {
153: Collection<MMObjectNode> transaction = getTransaction(transactionName);
154: MMObjectNode node = getTemporaryNodeManager().getNode(owner,
155: tmpnumber);
156: if (node != null) {
157: if (transaction.contains(node)) {
158: transaction.remove(node);
159: // } else {
160: // throw new TransactionManagerException("Node " + tmpnumber + " is not in transaction " + transactionName);
161: }
162: } else {
163: throw new TransactionManagerException("Node " + tmpnumber
164: + " doesn't exist.");
165: }
166: return tmpnumber;
167: }
168:
169: public String deleteObject(String transactionName, String owner,
170: String tmpnumber) throws TransactionManagerException {
171: Collection<MMObjectNode> transaction = getTransaction(transactionName);
172: MMObjectNode node = getTemporaryNodeManager().getNode(owner,
173: tmpnumber);
174: if (node != null) {
175: if (transaction.contains(node)) {
176: // Mark it as deleted
177: node.storeValue(MMObjectBuilder.TMP_FIELD_EXISTS,
178: EXISTS_NOLONGER);
179: } else {
180: throw new TransactionManagerException("Node "
181: + tmpnumber + " is not in transaction "
182: + transactionName);
183: }
184: } else {
185: throw new TransactionManagerException("Node " + tmpnumber
186: + " doesn't exist.");
187: }
188: return tmpnumber;
189: }
190:
191: public String cancel(Object user, String transactionName)
192: throws TransactionManagerException {
193: Collection<MMObjectNode> transaction = getTransaction(transactionName);
194: // remove nodes from the temporary node cache
195: MMObjectBuilder builder = MMBase.getMMBase().getTypeDef();
196: for (MMObjectNode node : transaction) {
197: builder.removeTmpNode(node
198: .getStringValue(MMObjectBuilder.TMP_FIELD_NUMBER));
199: }
200: deleteTransaction(transactionName);
201: if (log.isDebugEnabled()) {
202: log.debug("Removed transaction (after cancel) "
203: + transactionName + "\n" + transaction);
204: }
205: return transactionName;
206: }
207:
208: /**
209: * @todo Review this stuff..
210: * @since MMBase-1.9
211: */
212: public boolean resolve(String transactionName)
213: throws TransactionManagerException {
214:
215: // MM: I think we need an actual Transaction object! with e.g. a property 'resolved'.
216:
217: Collection<MMObjectNode> transaction = getTransaction(transactionName);
218: if (transaction instanceof ArrayList) { // a bit of a trick to see if it is resolved already
219: try {
220: getTransactionResolver().resolve(transaction);
221: transactions.put(transactionName, Collections
222: .unmodifiableCollection(transaction)); // makes it recognizable, and also the transaction is unusable after that
223: } catch (TransactionManagerException te) {
224: throw new TransactionManagerException(
225: "Can't resolve transaction " + transactionName
226: + " (it has " + transaction.size()
227: + " nodes", te);
228: }
229: } else {
230: log.service("Resolved already");
231: return false;
232: }
233: return true;
234: }
235:
236: public String commit(Object user, String transactionName)
237: throws TransactionManagerException {
238: Collection<MMObjectNode> transaction = getTransaction(transactionName);
239: try {
240: resolve(transactionName);
241: if (!performCommits(user, transaction)) {
242: throw new TransactionManagerException(
243: "Can't commit transaction " + transactionName);
244: }
245:
246: } finally {
247: // remove nodes from the temporary node cache
248: MMObjectBuilder builder = MMBase.getMMBase().getTypeDef();
249: for (MMObjectNode node : transaction) {
250: builder
251: .removeTmpNode(node
252: .getStringValue(MMObjectBuilder.TMP_FIELD_NUMBER));
253: }
254: deleteTransaction(transactionName);
255: if (log.isDebugEnabled()) {
256: log.debug("Removed transaction (after commit) "
257: + transactionName + "\n" + transaction);
258: }
259: }
260: return transactionName;
261: }
262:
263: private final static int UNCOMMITED = 0;
264: private final static int COMMITED = 1;
265: private final static int FAILED = 2;
266: private final static int NODE = 3;
267: private final static int RELATION = 4;
268:
269: boolean performCommits(Object user, Collection<MMObjectNode> nodes) {
270: if (nodes == null || nodes.size() == 0) {
271: log.debug("Empty list of nodes");
272: return true;
273: }
274:
275: int[] nodestate = new int[nodes.size()];
276: int[] nodeexist = new int[nodes.size()];
277: String username = findUserName(user);
278:
279: log.debug("Checking types and existance");
280:
281: int i = -1;
282: for (MMObjectNode node : nodes) {
283: i++;
284: // Nodes are uncommited by default
285: nodestate[i] = UNCOMMITED;
286: String exists = node
287: .getStringValue(MMObjectBuilder.TMP_FIELD_EXISTS);
288: if (exists == null) {
289: throw new IllegalStateException(
290: "The _exists field does not exist on node "
291: + node);
292: } else if (exists.equals(EXISTS_NO)) {
293: nodeexist[i] = I_EXISTS_NO;
294: } else if (exists.equals(EXISTS_YES)) {
295: nodeexist[i] = I_EXISTS_YES;
296: } else if (exists.equals(EXISTS_NOLONGER)) {
297: nodeexist[i] = I_EXISTS_NOLONGER;
298: } else {
299: throw new IllegalStateException(
300: "Invalid value for _exists on node " + node);
301: }
302: }
303:
304: log.debug("Commiting nodes");
305:
306: i = -1;
307: // First commit all the NODES
308: for (MMObjectNode node : nodes) {
309: i++;
310: if (!(node.getBuilder() instanceof InsRel)) {
311: if (nodeexist[i] == I_EXISTS_YES) {
312: if (!node.isChanged())
313: continue;
314: // use safe commit, which locks the node cache
315: boolean commitOK;
316: if (user instanceof UserContext) {
317: commitOK = node.commit((UserContext) user);
318: } else {
319: commitOK = node.parent.safeCommit(node);
320: }
321: if (commitOK) {
322: nodestate[i] = COMMITED;
323: } else {
324: nodestate[i] = FAILED;
325: }
326: } else if (nodeexist[i] == I_EXISTS_NO) {
327: int insertOK;
328: if (user instanceof UserContext) {
329: insertOK = node.insert((UserContext) user);
330: } else {
331: insertOK = node.parent.safeInsert(node,
332: username);
333: }
334: if (insertOK > 0) {
335: nodestate[i] = COMMITED;
336: } else {
337: nodestate[i] = FAILED;
338: String message = "When this failed, it is possible that the creation of an insrel went right, which leads to a database inconsistency.. stop now.. (transaction 2.0: [rollback?])";
339: throw new RuntimeException(message);
340: }
341: }
342: }
343: }
344:
345: log.debug("Commiting relations");
346:
347: // Then commit all the RELATIONS
348: i = -1;
349: for (MMObjectNode node : nodes) {
350: i++;
351: if (node.getBuilder() instanceof InsRel) {
352: // excactly the same code as 10 lines ago. Should be dispatched to some method..
353: if (nodeexist[i] == I_EXISTS_YES) {
354: if (!node.isChanged())
355: continue;
356: boolean commitOK;
357: if (user instanceof UserContext) {
358: commitOK = node.commit((UserContext) user);
359: } else {
360: commitOK = node.parent.safeCommit(node);
361: }
362: if (commitOK) {
363: nodestate[i] = COMMITED;
364: } else {
365: nodestate[i] = FAILED;
366: }
367: } else if (nodeexist[i] == I_EXISTS_NO) {
368: int insertOK;
369: if (user instanceof UserContext) {
370: insertOK = node.insert((UserContext) user);
371: } else {
372: insertOK = node.parent.safeInsert(node,
373: username);
374: }
375: if (insertOK > 0) {
376: nodestate[i] = COMMITED;
377: } else {
378: nodestate[i] = FAILED;
379: String message = "relation failed(transaction 2.0: [rollback?])";
380: log.error(message);
381: }
382: }
383: }
384: }
385:
386: log.debug("Deleting relations");
387:
388: // Then commit all the RELATIONS that must be deleted
389: i = -1;
390: for (MMObjectNode node : nodes) {
391: i++;
392: if (node.getBuilder() instanceof InsRel
393: && nodeexist[i] == I_EXISTS_NOLONGER) {
394: // no return information
395: if (user instanceof UserContext) {
396: node.remove((UserContext) user);
397: } else {
398: node.parent.removeNode(node);
399: }
400: nodestate[i] = COMMITED;
401: }
402: }
403:
404: log.debug("Deleting nodes");
405: // Then commit all the NODES that must be deleted
406: i = -1;
407: for (MMObjectNode node : nodes) {
408: i++;
409: if (!(node.getBuilder() instanceof InsRel)
410: && (nodeexist[i] == I_EXISTS_NOLONGER)) {
411: // no return information
412: if (user instanceof UserContext) {
413: node.remove((UserContext) user);
414: } else {
415: node.parent.removeNode(node);
416: }
417: nodestate[i] = COMMITED;
418: }
419: }
420:
421: // check for failures
422: boolean okay = true;
423: i = -1;
424: for (MMObjectNode node : nodes) {
425: i++;
426: if (nodestate[i] == FAILED) {
427: okay = false;
428: log.error("Failed node " + node.toString());
429: }
430: }
431: return okay;
432: }
433:
434: public String findUserName(Object user) {
435: if (user instanceof UserContext) {
436: return ((UserContext) user).getIdentifier();
437: } else {
438: return "";
439: }
440: }
441:
442: }
|