1 /**
2 * Copyright (C) 2006 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.opensymphony.xwork2.inject.util;
18
19 import static com.opensymphony.xwork2.inject.util.ReferenceType.STRONG;
20
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.util.concurrent.Callable;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentMap;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.FutureTask;
29
30 /**
31 * Extends {@link ReferenceMap} to support lazy loading values by overriding
32 * {@link #create(Object)}.
33 *
34 * @author crazybob@google.com (Bob Lee)
35 */
36 public abstract class ReferenceCache<K, V> extends ReferenceMap<K, V> {
37
38 private static final long serialVersionUID = 0;
39
40 transient ConcurrentMap<Object, Future<V>> futures =
41 new ConcurrentHashMap<Object, Future<V>>();
42
43 transient ThreadLocal<Future<V>> localFuture = new ThreadLocal<Future<V>>();
44
45 public ReferenceCache(ReferenceType keyReferenceType,
46 ReferenceType valueReferenceType) {
47 super(keyReferenceType, valueReferenceType);
48 }
49
50 /**
51 * Equivalent to {@code new ReferenceCache(STRONG, STRONG)}.
52 */
53 public ReferenceCache() {
54 super(STRONG, STRONG);
55 }
56
57 /**
58 * Override to lazy load values. Use as an alternative to {@link
59 * #put(Object,Object)}. Invoked by getter if value isn't already cached.
60 * Must not return {@code null}. This method will not be called again until
61 * the garbage collector reclaims the returned value.
62 */
63 protected abstract V create(K key);
64
65 V internalCreate(K key) {
66 try {
67 FutureTask<V> futureTask = new FutureTask<V>(
68 new CallableCreate(key));
69
70 // use a reference so we get the same equality semantics.
71 Object keyReference = referenceKey(key);
72 Future<V> future = futures.putIfAbsent(keyReference, futureTask);
73 if (future == null) {
74 // winning thread.
75 try {
76 if (localFuture.get() != null) {
77 throw new IllegalStateException(
78 "Nested creations within the same cache are not allowed.");
79 }
80 localFuture.set(futureTask);
81 futureTask.run();
82 V value = futureTask.get();
83 putStrategy().execute(this,
84 keyReference, referenceValue(keyReference, value));
85 return value;
86 } finally {
87 localFuture.remove();
88 futures.remove(keyReference);
89 }
90 } else {
91 // wait for winning thread.
92 return future.get();
93 }
94 } catch (InterruptedException e) {
95 throw new RuntimeException(e);
96 } catch (ExecutionException e) {
97 Throwable cause = e.getCause();
98 if (cause instanceof RuntimeException) {
99 throw (RuntimeException) cause;
100 } else if (cause instanceof Error) {
101 throw (Error) cause;
102 }
103 throw new RuntimeException(cause);
104 }
105 }
106
107 /**
108 * {@inheritDoc}
109 *
110 * If this map does not contain an entry for the given key and {@link
111 * #create(Object)} has been overridden, this method will create a new
112 * value, put it in the map, and return it.
113 *
114 * @throws NullPointerException if {@link #create(Object)} returns null.
115 * @throws java.util.concurrent.CancellationException if the creation is
116 * cancelled. See {@link #cancel()}.
117 */
118 @SuppressWarnings("unchecked")
119 @Override public V get(final Object key) {
120 V value = super.get(key);
121 return (value == null)
122 ? internalCreate((K) key)
123 : value;
124 }
125
126 /**
127 * Cancels the current {@link #create(Object)}. Throws {@link
128 * java.util.concurrent.CancellationException} to all clients currently
129 * blocked on {@link #get(Object)}.
130 */
131 protected void cancel() {
132 Future<V> future = localFuture.get();
133 if (future == null) {
134 throw new IllegalStateException("Not in create().");
135 }
136 future.cancel(false);
137 }
138
139 class CallableCreate implements Callable<V> {
140
141 K key;
142
143 public CallableCreate(K key) {
144 this.key = key;
145 }
146
147 public V call() {
148 // try one more time (a previous future could have come and gone.)
149 V value = internalGet(key);
150 if (value != null) {
151 return value;
152 }
153
154 // create value.
155 value = create(key);
156 if (value == null) {
157 throw new NullPointerException(
158 "create(K) returned null for: " + key);
159 }
160 return value;
161 }
162 }
163
164 /**
165 * Returns a {@code ReferenceCache} delegating to the specified {@code
166 * function}. The specified function must not return {@code null}.
167 */
168 public static <K, V> ReferenceCache<K, V> of(
169 ReferenceType keyReferenceType,
170 ReferenceType valueReferenceType,
171 final Function<? super K, ? extends V> function) {
172 ensureNotNull(function);
173 return new ReferenceCache<K, V>(keyReferenceType, valueReferenceType) {
174 protected V create(K key) {
175 return function.apply(key);
176 }
177 private static final long serialVersionUID = 0;
178 };
179 }
180
181 private void readObject(ObjectInputStream in) throws IOException,
182 ClassNotFoundException {
183 in.defaultReadObject();
184 this.futures = new ConcurrentHashMap<Object, Future<V>>();
185 this.localFuture = new ThreadLocal<Future<V>>();
186 }
187
188 }