1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.logging.impl;
19
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.WeakReference;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Enumeration;
25 import java.util.HashSet;
26 import java.util.Hashtable;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Set;
31
32 /**
33 * Implements {@code Hashtable} to use {@code WeakReference}'s
34 * to hold its keys thus allowing them to be reclaimed by the garbage collector.
35 * The associated values are retained using strong references.
36 * <p>
37 * This class follows the semantics of {@code Hashtable} as closely as
38 * possible. It therefore does not accept null values or keys.
39 * </p>
40 * <p>
41 * <strong>Note:</strong>
42 * This is <em>not</em> intended to be a general purpose hash table replacement.
43 * This implementation is also tuned towards a particular purpose: for use as a replacement
44 * for {@code Hashtable} in {@code LogFactory}. This application requires
45 * good liveliness for {@code get} and {@code put}. Various tradeoffs
46 * have been made with this in mind.
47 * </p>
48 * <p>
49 * <strong>Usage:</strong> typical use case is as a drop-in replacement
50 * for the {@code Hashtable} used in {@code LogFactory} for J2EE environments
51 * running 1.3+ JVMs. Use of this class <em>in most cases</em> (see below) will
52 * allow class loaders to be collected by the garbage collector without the need
53 * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
54 * </p>
55 * <p>
56 * {@code org.apache.commons.logging.LogFactory} checks whether this class
57 * can be supported by the current JVM, and if so then uses it to store
58 * references to the {@code LogFactory} implementation it loads
59 * (rather than using a standard Hashtable instance).
60 * Having this class used instead of {@code Hashtable} solves
61 * certain issues related to dynamic reloading of applications in J2EE-style
62 * environments. However this class requires Java 1.3 or later (due to its use
63 * of {@link java.lang.ref.WeakReference} and associates).
64 * And by the way, this extends {@code Hashtable} rather than {@code HashMap}
65 * for backwards compatibility reasons. See the documentation
66 * for method {@code LogFactory.createFactoryStore} for more details.
67 * </p>
68 * <p>
69 * The reason all this is necessary is due to a issue which
70 * arises during hot deploy in a J2EE-like containers.
71 * Each component running in the container owns one or more class loaders; when
72 * the component loads a LogFactory instance via the component class loader
73 * a reference to it gets stored in the static LogFactory.factories member,
74 * keyed by the component's class loader so different components don't
75 * stomp on each other. When the component is later unloaded, the container
76 * sets the component's class loader to null with the intent that all the
77 * component's classes get garbage-collected. However there's still a
78 * reference to the component's class loader from a key in the "global"
79 * {@code LogFactory}'s factories member! If {@code LogFactory.release()}
80 * is called whenever component is unloaded, the class loaders will be correctly
81 * garbage collected; this <em>should</em> be done by any container that
82 * bundles commons-logging by default. However, holding the class loader
83 * references weakly ensures that the class loader will be garbage collected
84 * without the container performing this step.
85 * </p>
86 * <p>
87 * <strong>Limitations:</strong>
88 * There is still one (unusual) scenario in which a component will not
89 * be correctly unloaded without an explicit release. Though weak references
90 * are used for its keys, it is necessary to use strong references for its values.
91 * </p>
92 * <p>
93 * If the abstract class {@code LogFactory} is
94 * loaded by the container class loader but a subclass of
95 * {@code LogFactory} [LogFactory1] is loaded by the component's
96 * class loader and an instance stored in the static map associated with the
97 * base LogFactory class, then there is a strong reference from the LogFactory
98 * class to the LogFactory1 instance (as normal) and a strong reference from
99 * the LogFactory1 instance to the component class loader via
100 * {@code getClass().getClassLoader()}. This chain of references will prevent
101 * collection of the child class loader.
102 * </p>
103 * <p>
104 * Such a situation occurs when the commons-logging.jar is
105 * loaded by a parent class loader (e.g. a server level class loader in a
106 * servlet container) and a custom {@code LogFactory} implementation is
107 * loaded by a child class loader (e.g. a web app class loader).
108 * </p>
109 * <p>
110 * To avoid this scenario, ensure
111 * that any custom LogFactory subclass is loaded by the same class loader as
112 * the base {@code LogFactory}. Creating custom LogFactory subclasses is,
113 * however, rare. The standard LogFactoryImpl class should be sufficient
114 * for most or all users.
115 * </p>
116 *
117 * @since 1.1
118 * @deprecated No longer used, will be removed in 2.0.
119 */
120 @Deprecated
121 public final class WeakHashtable extends Hashtable {
122
123 /** Entry implementation */
124 private final static class Entry implements Map.Entry {
125
126 private final Object key;
127 private final Object value;
128
129 private Entry(final Object key, final Object value) {
130 this.key = key;
131 this.value = value;
132 }
133
134 @Override
135 public boolean equals(final Object o) {
136 boolean result = false;
137 if (o instanceof Map.Entry) {
138 final Map.Entry entry = (Map.Entry) o;
139 result = (getKey()==null ?
140 entry.getKey() == null :
141 getKey().equals(entry.getKey())) &&
142 (getValue()==null ?
143 entry.getValue() == null :
144 getValue().equals(entry.getValue()));
145 }
146 return result;
147 }
148
149 @Override
150 public Object getKey() {
151 return key;
152 }
153
154 @Override
155 public Object getValue() {
156 return value;
157 }
158
159 @Override
160 public int hashCode() {
161 return (getKey()==null ? 0 : getKey().hashCode()) ^
162 (getValue()==null ? 0 : getValue().hashCode());
163 }
164
165 @Override
166 public Object setValue(final Object value) {
167 throw new UnsupportedOperationException("Entry.setValue is not supported.");
168 }
169 }
170
171 /** Wrapper giving correct symantics for equals and hash code */
172 private final static class Referenced {
173
174 private final WeakReference reference;
175 private final int hashCode;
176
177 /**
178 *
179 * @throws NullPointerException if referant is {@code null}
180 */
181 private Referenced(final Object referant) {
182 reference = new WeakReference(referant);
183 // Calc a permanent hashCode so calls to Hashtable.remove()
184 // work if the WeakReference has been cleared
185 hashCode = referant.hashCode();
186 }
187
188 /**
189 *
190 * @throws NullPointerException if key is {@code null}
191 */
192 private Referenced(final Object key, final ReferenceQueue queue) {
193 reference = new WeakKey(key, queue, this);
194 // Calc a permanent hashCode so calls to Hashtable.remove()
195 // work if the WeakReference has been cleared
196 hashCode = key.hashCode();
197
198 }
199
200 @Override
201 public boolean equals(final Object o) {
202 boolean result = false;
203 if (o instanceof Referenced) {
204 final Referenced otherKey = (Referenced) o;
205 final Object thisKeyValue = getValue();
206 final Object otherKeyValue = otherKey.getValue();
207 if (thisKeyValue == null) {
208 result = otherKeyValue == null;
209
210 // Since our hash code was calculated from the original
211 // non-null referant, the above check breaks the
212 // hash code/equals contract, as two cleared Referenced
213 // objects could test equal but have different hash codes.
214 // We can reduce (not eliminate) the chance of this
215 // happening by comparing hash codes.
216 result = result && hashCode() == otherKey.hashCode();
217 // In any case, as our constructor does not allow null referants
218 // and Hashtable does not do equality checks between
219 // existing keys, normal hash table operations should never
220 // result in an equals comparison between null referants
221 }
222 else
223 {
224 result = thisKeyValue.equals(otherKeyValue);
225 }
226 }
227 return result;
228 }
229
230 private Object getValue() {
231 return reference.get();
232 }
233
234 @Override
235 public int hashCode() {
236 return hashCode;
237 }
238 }
239
240 /**
241 * WeakReference subclass that holds a hard reference to an
242 * associated {@code value} and also makes accessible
243 * the Referenced object holding it.
244 */
245 private final static class WeakKey extends WeakReference {
246
247 private final Referenced referenced;
248
249 private WeakKey(final Object key,
250 final ReferenceQueue queue,
251 final Referenced referenced) {
252 super(key, queue);
253 this.referenced = referenced;
254 }
255
256 private Referenced getReferenced() {
257 return referenced;
258 }
259 }
260
261 /** Serializable version identifier. */
262 private static final long serialVersionUID = -1546036869799732453L;
263
264 /**
265 * The maximum number of times put() or remove() can be called before
266 * the map will be purged of all cleared entries.
267 */
268 private static final int MAX_CHANGES_BEFORE_PURGE = 100;
269
270 /**
271 * The maximum number of times put() or remove() can be called before
272 * the map will be purged of one cleared entry.
273 */
274 private static final int PARTIAL_PURGE_COUNT = 10;
275
276 /** ReferenceQueue we check for GC'd keys. */
277 private final transient ReferenceQueue queue = new ReferenceQueue();
278
279 /** Counter used to control how often we purge gc'd entries. */
280 private int changeCount;
281
282 /**
283 * Constructs a WeakHashtable with the Hashtable default
284 * capacity and load factor.
285 */
286 public WeakHashtable() {}
287
288 /**
289 *@see Hashtable
290 */
291 @Override
292 public boolean containsKey(final Object key) {
293 // purge should not be required
294 final Referenced referenced = new Referenced(key);
295 return super.containsKey(referenced);
296 }
297
298 /**
299 *@see Hashtable
300 */
301 @Override
302 public Enumeration elements() {
303 purge();
304 return super.elements();
305 }
306
307 /**
308 *@see Hashtable
309 */
310 @Override
311 public Set entrySet() {
312 purge();
313 final Set referencedEntries = super.entrySet();
314 final Set unreferencedEntries = new HashSet();
315 for (final Object referencedEntry : referencedEntries) {
316 final Map.Entry entry = (Map.Entry) referencedEntry;
317 final Referenced referencedKey = (Referenced) entry.getKey();
318 final Object key = referencedKey.getValue();
319 final Object value = entry.getValue();
320 if (key != null) {
321 final Entry dereferencedEntry = new Entry(key, value);
322 unreferencedEntries.add(dereferencedEntry);
323 }
324 }
325 return unreferencedEntries;
326 }
327
328 /**
329 *@see Hashtable
330 */
331 @Override
332 public Object get(final Object key) {
333 // for performance reasons, no purge
334 final Referenced referenceKey = new Referenced(key);
335 return super.get(referenceKey);
336 }
337
338 /**
339 *@see Hashtable
340 */
341 @Override
342 public boolean isEmpty() {
343 purge();
344 return super.isEmpty();
345 }
346
347 /**
348 *@see Hashtable
349 */
350 @Override
351 public Enumeration keys() {
352 purge();
353 final Enumeration enumer = super.keys();
354 return new Enumeration() {
355 @Override
356 public boolean hasMoreElements() {
357 return enumer.hasMoreElements();
358 }
359 @Override
360 public Object nextElement() {
361 final Referenced nextReference = (Referenced) enumer.nextElement();
362 return nextReference.getValue();
363 }
364 };
365 }
366
367 /**
368 *@see Hashtable
369 */
370 @Override
371 public Set keySet() {
372 purge();
373 final Set referencedKeys = super.keySet();
374 final Set unreferencedKeys = new HashSet();
375 for (final Object referencedKey : referencedKeys) {
376 final Referenced referenceKey = (Referenced) referencedKey;
377 final Object keyValue = referenceKey.getValue();
378 if (keyValue != null) {
379 unreferencedKeys.add(keyValue);
380 }
381 }
382 return unreferencedKeys;
383 }
384
385 /**
386 * Purges all entries whose wrapped keys
387 * have been garbage collected.
388 */
389 private void purge() {
390 final List toRemove = new ArrayList();
391 synchronized (queue) {
392 WeakKey key;
393 while ((key = (WeakKey) queue.poll()) != null) {
394 toRemove.add(key.getReferenced());
395 }
396 }
397
398 // LOGGING-119: do the actual removal of the keys outside the sync block
399 // to prevent deadlock scenarios as purge() may be called from
400 // non-synchronized methods too
401 final int size = toRemove.size();
402 for (int i = 0; i < size; i++) {
403 super.remove(toRemove.get(i));
404 }
405 }
406
407 /**
408 * Purges one entry whose wrapped key
409 * has been garbage collected.
410 */
411 private void purgeOne() {
412 synchronized (queue) {
413 final WeakKey key = (WeakKey) queue.poll();
414 if (key != null) {
415 super.remove(key.getReferenced());
416 }
417 }
418 }
419
420 /**
421 *@see Hashtable
422 */
423 @Override
424 public synchronized Object put(final Object key, final Object value) {
425 // check for nulls, ensuring semantics match superclass
426 Objects.requireNonNull(key, "key");
427 Objects.requireNonNull(value, "value");
428
429 // for performance reasons, only purge every
430 // MAX_CHANGES_BEFORE_PURGE times
431 if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
432 purge();
433 changeCount = 0;
434 }
435 // do a partial purge more often
436 else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
437 purgeOne();
438 }
439
440 final Referenced keyRef = new Referenced(key, queue);
441 return super.put(keyRef, value);
442 }
443
444 /**
445 *@see Hashtable
446 */
447 @Override
448 public void putAll(final Map t) {
449 if (t != null) {
450 final Set entrySet = t.entrySet();
451 for (final Object element : entrySet) {
452 final Map.Entry entry = (Map.Entry) element;
453 put(entry.getKey(), entry.getValue());
454 }
455 }
456 }
457
458 /**
459 * @see Hashtable
460 */
461 @Override
462 protected void rehash() {
463 // purge here to save the effort of rehashing dead entries
464 purge();
465 super.rehash();
466 }
467
468 /**
469 *@see Hashtable
470 */
471 @Override
472 public synchronized Object remove(final Object key) {
473 // for performance reasons, only purge every
474 // MAX_CHANGES_BEFORE_PURGE times
475 if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
476 purge();
477 changeCount = 0;
478 }
479 // do a partial purge more often
480 else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
481 purgeOne();
482 }
483 return super.remove(new Referenced(key));
484 }
485
486 /**
487 *@see Hashtable
488 */
489 @Override
490 public int size() {
491 purge();
492 return super.size();
493 }
494
495 /**
496 *@see Hashtable
497 */
498 @Override
499 public String toString() {
500 purge();
501 return super.toString();
502 }
503
504 /**
505 *@see Hashtable
506 */
507 @Override
508 public Collection values() {
509 purge();
510 return super.values();
511 }
512 }