-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
/
BulkChange.java
167 lines (152 loc) · 5.27 KB
/
BulkChange.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson;
import hudson.model.Saveable;
import java.io.Closeable;
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>
* try (BulkChange bc = new BulkChange(someObject)) {
* ... make changes to 'someObject'
* bc.commit();
* }
* </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>
* Mutator 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 jenkins.model.Jenkins#save()} as an example if you are not sure how to implement {@link Saveable}.
*
* @author Kohsuke Kawaguchi
* @since 1.249
*/
public class BulkChange implements Closeable {
private final Saveable saveable;
public final Exception allocator;
private final BulkChange parent;
private boolean completed;
public BulkChange(Saveable saveable) {
this.parent = current();
this.saveable = saveable;
// remember 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();
}
/**
* Alias for {@link #abort()} to make {@link BulkChange} auto-closeable.
*/
@Override
public void close() {
abort();
}
/**
* 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<>();
/**
* 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() {
}
};
}