File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\util\KeyedDataStorage.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.util;
25
26 import hudson.model.Fingerprint;
27 import hudson.model.FingerprintMap;
28
29 import java.io.IOException;
30 import java.lang.ref.SoftReference;
31 import java.text.MessageFormat;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 /**
36 * Convenient base class for implementing data storage.
37 *
38 * <p>
39 * One typical pattern of data storage in Hudson is the one that {@link Fingerprint}
40 * uses, where each data is keyed by an unique key (MD5 sum), and that key is used
41 * to determine the file system location of the data.
42 *
43 * On memory, each data is represented by one object ({@link Fingerprint}), and
44 * write access to the same data is coordinated by using synchronization.
45 *
46 * <p>
47 * With such storage, care has to be taken to ensure that there's only one data
48 * object in memory for any given key. That means load and create operation
49 * needs to be synchronized. This class implements this logic in a fairly efficient
50 * way, and thus intends to help plugins that want to use such data storage.
51 *
52 * @since 1.87
53 * @author Kohsuke Kawaguchi
54 * @see FingerprintMap
55 */
56 public abstract class KeyedDataStorage<T,P> {
57 /**
58 * The value is either {@code SoftReference<Fingerprint>} or {@link Loading}.
59 *
60 * If it's {@link SoftReference}, that represents the currently available value.
61 * If it's {@link Loading}, then that indicates the fingerprint is being loaded.
62 * The thread can wait on this object to be notified when the loading completes.
63 */
64 private final ConcurrentHashMap<String,Object> core = new ConcurrentHashMap<String,Object>();
65
66 /**
67 * Used in {@link KeyedDataStorage#core} to indicate that the loading of a fingerprint
68 * is in progress, so that we can avoid creating two {@link Fingerprint}s for the same hash code,
69 * but do so without having a single lock.
70 */
71 private static class Loading<T> {
72 private T value;
73 private boolean set;
74
75 public synchronized void set(T value) {
76 this.set = true;
77 this.value = value;
78 notifyAll();
79 }
80
81 /**
82 * Blocks until the value is {@link #set(Object)} by another thread
83 * and returns the value.
84 */
85 public synchronized T get() {
86 try {
87 while(!set)
88 wait();
89 return value;
90 } catch (InterruptedException e) {
91 // assume the loading failed, but make sure we process interruption properly later
92 Thread.currentThread().interrupt();
93 return null;
94 }
95 }
96 }
97
98 /**
99 * Atomically gets the existing data object if any, or if it doesn't exist
100 * {@link #create(String,Object) create} it and return it.
101 *
102 * @return
103 * Never null.
104 * @param createParams
105 * Additional parameters needed to create a new data object. Can be null.
106 */
107 public T getOrCreate(String key, P createParams) throws IOException {
108 return get(key,true,createParams);
109 }
110
111 /**
112 * Finds the data object that matches the given key if available, or null
113 * if not found.
114 */
115 public T get(String key) throws IOException {
116 return get(key,false,null);
117 }
118
119 /**
120 * Implementation of get/getOrCreate.
121 */
122 protected T get(String key, boolean createIfNotExist, P createParams) throws IOException {
123 while(true) {
124 totalQuery.incrementAndGet();
125 Object value = core.get(key);
126
127 if(value instanceof SoftReference) {
128 SoftReference<T> wfp = (SoftReference<T>) value;
129 T t = wfp.get();
130 if(t!=null) {
131 cacheHit.incrementAndGet();
132 return t; // found it
133 }
134 weakRefLost.incrementAndGet();
135 }
136 if(value instanceof Loading) {
137 // another thread is loading it. get the value from there.
138 T t = ((Loading<T>)value).get();
139 if(t!=null || !createIfNotExist)
140 return t; // found it (t!=null) or we are just 'get' (!createIfNotExist)
141 }
142
143 // the fingerprint doesn't seem to be loaded thus far, so let's load it now.
144 // the care needs to be taken that other threads might be trying to do the same.
145 Loading<T> l = new Loading<T>();
146 if(value==null ? core.putIfAbsent(key,l)!=null : !core.replace(key,value,l)) {
147 // the value has changed since then. another thread is attempting to do the same.
148 // go back to square 1 and try it again.
149 continue;
150 }
151
152 T t = null;
153 try {
154 t = load(key);
155 if(t==null && createIfNotExist) {
156 t = create(key,createParams); // create the new data
157 if(t==null)
158 throw new IllegalStateException(); // bug in the derived classes
159 }
160 } catch(IOException e) {
161 loadFailure.incrementAndGet();
162 throw e;
163 } finally {
164 // let other threads know that the value is available now.
165 // when the original thread failed to load, this should set it to null.
166 l.set(t);
167 }
168
169 // the map needs to be updated to reflect the result of loading
170 if(t!=null)
171 core.put(key,new SoftReference<T>(t));
172 else
173 core.remove(key);
174
175 return t;
176 }
177
178 }
179
180 /**
181 * Attempts to load an existing data object from disk.
182 *
183 * <p>
184 * {@link KeyedDataStorage} class serializes the requests so that
185 * no two threads call the {@link #load(String)} method with the
186 * same parameter concurrently. This ensures that there's only
187 * up to one data object for any key.
188 *
189 * @return
190 * null if no such data exists.
191 * @throws IOException
192 * if load operation fails. This exception will be
193 * propagated to the caller.
194 */
195 protected abstract T load(String key) throws IOException;
196
197 /**
198 * Creates a new data object.
199 *
200 * <p>
201 * This method is called by {@link #getOrCreate(String,Object)}
202 * if the data that matches the specified key does not exist.
203 * <p>
204 * Because of concurrency, another thread might call {@link #get(String)}
205 * as soon as a new data object is created, so it's important that
206 * this method returns a properly initialized "valid" object.
207 *
208 * @return
209 * never null. If construction fails, abort with an exception.
210 * @throws IOException
211 * if the method fails to create a new data object, it can throw
212 * {@link IOException} (or any other exception) and that will be
213 * propagated to the caller.
214 */
215 protected abstract T create(String key, P createParams) throws IOException;
216
217 public void resetPerformanceStats() {
218 totalQuery.set(0);
219 cacheHit.set(0);
220 weakRefLost.set(0);
221 loadFailure.set(0);
222 }
223
224 /**
225 * Gets the short summary of performance statistics.
226 */
227 public String getPerformanceStats() {
228 int total = totalQuery.get();
229 int hit = cacheHit.get();
230 int weakRef = weakRefLost.get();
231 int failure = loadFailure.get();
232 int miss = total-hit-weakRef;
233
234 return MessageFormat.format("total={0} hit={1}% lostRef={2}% failure={3}% miss={4}%",
235 total,hit,weakRef,failure,miss);
236 }
237
238 /**
239 * Total number of queries into this storage.
240 */
241 public final AtomicInteger totalQuery = new AtomicInteger();
242 /**
243 * Number of cache hits (of all the total queries.)
244 */
245 public final AtomicInteger cacheHit = new AtomicInteger();
246 /**
247 * Among cache misses, number of times when we had {@link SoftReference}
248 * but lost its value due to GC.
249 *
250 * <tt>totalQuery-cacheHit-weakRefLost</tt> means cache miss.
251 */
252 public final AtomicInteger weakRefLost = new AtomicInteger();
253 /**
254 * Number of failures in loading data.
255 */
256 public final AtomicInteger loadFailure = new AtomicInteger();
257 }
258