/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi
*
*
*******************************************************************************/
package hudson;
import hudson.model.Saveable;
import hudson.model.Hudson;
import java.io.IOException;
/**
* Transaction-like object that can be used to make a bunch of changes to an
* object, and defer the {@link Saveable#save()} until the end.
*
* <p> The usage of {@link BulkChange} needs to follow a specific closure-like
* pattern, namely:
*
* <pre>
* BulkChange bc = new BulkChange(someObject);
* try {
* ... make changes to 'someObject'
* } finally {
* bc.commit();
* }
* </pre>
*
* <p> ... or if you'd like to avoid saving when something bad happens:
*
* <pre>
* BulkChange bc = new BulkChange(someObject);
* try {
* ... make changes to 'someObject'
* bc.commit();
* } finally {
* bc.abort();
* }
* </pre>
*
* <p> Use of this method is optional. If {@link BulkChange} is not used,
* individual mutator will perform the save operation, and things will just run
* somewhat slower.
*
*
* <h2>Cooperation from {@link Saveable}</h2> <p> For this class to work as
* intended, {@link Saveable} implementations need to co-operate. Namely,
*
* <ol> <li> Mutater methods should invoke {@code this.save()} so that if the
* method is called outside a {@link BulkChange}, the result will be saved
* immediately.
*
* <li> In the {@code save()} method implementation, use
* {@link #contains(Saveable)} and only perform the actual I/O operation when
* this method returns false. </ol>
*
* <p> See {@link Hudson#save()} as an example if you are not sure how to
* implement {@link Saveable}.
*
* @author Kohsuke Kawaguchi
* @since 1.249
*/
public class BulkChange {
private final Saveable saveable;
//TODO: review and check whether we can do it private
public final Exception allocator;
public Exception getAllocator() {
return allocator;
}
private final BulkChange parent;
private boolean completed;
public BulkChange(Saveable saveable) {
this.parent = current();
this.saveable = saveable;
// rememeber who allocated this object in case
// someone forgot to call save() at the end.
allocator = new Exception();
// in effect at construction
INSCOPE.set(this);
}
/**
* Saves the accumulated changes.
*/
public void commit() throws IOException {
if (completed) {
return;
}
completed = true;
// move this object out of the scope first before save, or otherwise the save() method will do nothing.
pop();
saveable.save();
}
/**
* Exits the scope of {@link BulkChange} without saving the changes.
*
* <p> This can be used when a bulk change fails in the middle. Note that
* unlike a real transaction, this will not roll back the state of the
* object.
*
* <p> The abort method can be called after the commit method, in which case
* this method does nothing. This is so that {@link BulkChange} can be used
* naturally in the try/finally block.
*/
public void abort() {
if (completed) {
return;
}
completed = true;
pop();
}
private void pop() {
if (current() != this) {
throw new AssertionError("Trying to save BulkChange that's not in scope");
}
INSCOPE.set(parent);
}
/**
* {@link BulkChange}s that are effective currently.
*/
private static final ThreadLocal<BulkChange> INSCOPE = new ThreadLocal<BulkChange>();
/**
* Gets the {@link BulkChange} instance currently in scope for the current
* thread.
*/
public static BulkChange current() {
return INSCOPE.get();
}
/**
* Checks if the given {@link Saveable} is currently in the bulk change.
*
* <p> The expected usage is from the {@link Saveable#save()} implementation
* to check if the actual persistence should happen now or not.
*/
public static boolean contains(Saveable s) {
for (BulkChange b = current(); b != null; b = b.parent) {
if (b.saveable == s || b.saveable == ALL) {
return true;
}
}
return false;
}
/**
* Magic {@link Saveable} instance that can make {@link BulkChange} veto all
* the save operations by making the {@link #contains(Saveable)} method
* return true for everything.
*/
public static final Saveable ALL = new Saveable() {
public void save() {
}
};
}