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 package org.archive.io;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.NoSuchElementException;
30 import java.util.concurrent.atomic.AtomicInteger;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33
34 import org.apache.commons.pool.BasePoolableObjectFactory;
35 import org.apache.commons.pool.impl.FairGenericObjectPool;
36 import org.apache.commons.pool.impl.GenericObjectPool;
37
38 /***
39 * Pool of Writers.
40 *
41 * Abstract. Override and pass in the Constructor a factory that creates
42 * {@link WriterPoolMember} implementations.
43 *
44 * @author stack
45 */
46 public abstract class WriterPool {
47 final Logger logger = Logger.getLogger(this.getClass().getName());
48
49 /***
50 * Used to generate unique filename sequences.
51 */
52 final private AtomicInteger serialNo;
53
54 /***
55 * Don't enforce a maximum number of idle instances in pool.
56 * To do so means GenericObjectPool will close files prematurely.
57 */
58 protected static final int NO_MAX_IDLE = -1;
59
60 /***
61 * Retry getting a file on fail the below arbitrary amount of times.
62 * This facility is not configurable. If we fail this many times
63 * getting a file, something is seriously wrong.
64 */
65 private final int arbitraryRetryMax = 10;
66
67 /***
68 * Default maximum active number of files in the pool.
69 */
70 public static final int DEFAULT_MAX_ACTIVE = 1;
71
72 /***
73 * Maximum time to wait on a free file..
74 */
75 public static final int DEFAULT_MAXIMUM_WAIT = 1000 * 60 * 5;
76
77 /***
78 * Pool instance.
79 */
80 private GenericObjectPool pool = null;
81
82 /***
83 * File settings.
84 * Keep in data structure rather than as individual values.
85 */
86 private final WriterPoolSettings settings;
87
88 /***
89 * Shutdown default constructor.
90 */
91 private WriterPool() {
92 this(null, null, null, -1, -1);
93 }
94
95 /***
96 * Constructor
97 * @param serial Used to generate unique filename sequences
98 * @param factory Factory that knows how to make a {@link WriterPoolMember}.
99 * @param settings Settings for this pool.
100 * @param poolMaximumActive
101 * @param poolMaximumWait
102 */
103 public WriterPool(final AtomicInteger serial,
104 final BasePoolableObjectFactory factory,
105 final WriterPoolSettings settings,
106 final int poolMaximumActive, final int poolMaximumWait) {
107 logger.info("Initial configuration:" +
108 " prefix=" + settings.getPrefix() +
109 ", suffix=" + settings.getSuffix() +
110 ", compress=" + settings.isCompressed() +
111 ", maxSize=" + settings.getMaxSize() +
112 ", maxActive=" + poolMaximumActive +
113 ", maxWait=" + poolMaximumWait);
114 this.settings = settings;
115 this.pool = new FairGenericObjectPool(factory, poolMaximumActive,
116 GenericObjectPool.WHEN_EXHAUSTED_BLOCK, poolMaximumWait,
117 NO_MAX_IDLE);
118 this.serialNo = serial;
119 }
120
121 /***
122 * Check out a {@link WriterPoolMember}.
123 *
124 * This method must be answered by a call to
125 * {@link #returnFile(WriterPoolMember)} else pool starts leaking.
126 *
127 * @return Writer checked out of a pool of files.
128 * @throws IOException Problem getting Writer from pool (Converted
129 * from Exception to IOException so this pool can live as a good citizen
130 * down in depths of ARCSocketFactory).
131 * @throws NoSuchElementException If we time out waiting on a pool member.
132 */
133 public WriterPoolMember borrowFile()
134 throws IOException {
135 WriterPoolMember f = null;
136 for (int i = 0; f == null; i++) {
137 long waitStart = System.currentTimeMillis();
138 try {
139 f = (WriterPoolMember)this.pool.borrowObject();
140 if (logger.getLevel() == Level.FINE) {
141 logger.fine("Borrowed " + f + " (Pool State: "
142 + getPoolState(waitStart) + ").");
143 }
144 } catch (NoSuchElementException e) {
145
146
147 logger.warning(e.getMessage() + ": Retry #" + i + " of "
148 + " max of " + arbitraryRetryMax
149 + ": NSEE Pool State: " + getPoolState(waitStart));
150 if (i >= arbitraryRetryMax) {
151 logger.log(Level.SEVERE,
152 "maximum retries exceeded; rethrowing",e);
153 throw e;
154 }
155 } catch (Exception e) {
156
157 logger.log(Level.SEVERE,"E Pool State: " +
158 getPoolState(waitStart), e);
159 throw new IOException("Failed getting writer from pool: " +
160 e.getMessage());
161 }
162 }
163 return f;
164 }
165
166 /***
167 * @param writer Writer to return to the pool.
168 * @throws IOException Problem returning File to pool.
169 */
170 public void returnFile(WriterPoolMember writer)
171 throws IOException {
172 try {
173 if (logger.getLevel() == Level.FINE) {
174 logger.fine("Returned " + writer);
175 }
176 this.pool.returnObject(writer);
177 }
178 catch(Exception e)
179 {
180 throw new IOException("Failed restoring writer to pool: " +
181 e.getMessage());
182 }
183 }
184
185 public void invalidateFile(WriterPoolMember f)
186 throws IOException {
187 try {
188 this.pool.invalidateObject(f);
189 } catch (Exception e) {
190
191 throw new IOException(e.getMessage());
192 }
193
194
195 File file = f.getFile();
196 file.renameTo(new File(file.getAbsoluteFile() +
197 WriterPoolMember.INVALID_SUFFIX));
198 }
199
200 /***
201 * @return Number of {@link WriterPoolMember}s checked out of pool.
202 * @throws java.lang.UnsupportedOperationException
203 */
204 public int getNumActive()
205 throws UnsupportedOperationException {
206 return this.pool.getNumActive();
207 }
208
209 /***
210 * @return Number of {@link WriterPoolMember} instances still in the pool.
211 * @throws java.lang.UnsupportedOperationException
212 */
213 public int getNumIdle()
214 throws UnsupportedOperationException {
215 return this.pool.getNumIdle();
216 }
217
218 /***
219 * Close all {@link WriterPoolMember}s in pool.
220 */
221 public void close() {
222 this.pool.clear();
223 }
224
225 /***
226 * @return Returns settings.
227 */
228 public WriterPoolSettings getSettings() {
229 return this.settings;
230 }
231
232 /***
233 * @return State of the pool string
234 */
235 protected String getPoolState() {
236 return getPoolState(-1);
237 }
238
239 /***
240 * @param startTime If we are passed a start time, we'll add difference
241 * between it and now to end of string. Pass -1 if don't want this
242 * added to end of state string.
243 * @return State of the pool string
244 */
245 protected String getPoolState(long startTime) {
246 StringBuffer buffer = new StringBuffer("Active ");
247 buffer.append(getNumActive());
248 buffer.append(" of max ");
249 buffer.append(this.pool.getMaxActive());
250 buffer.append(", idle ");
251 buffer.append(this.pool.getNumIdle());
252 if (startTime != -1) {
253 buffer.append(", time ");
254 buffer.append(System.currentTimeMillis() - startTime);
255 buffer.append("ms of max ");
256 buffer.append(this.pool.getMaxWait());
257 buffer.append("ms");
258 }
259 return buffer.toString();
260 }
261
262 /***
263 * Returns the atomic integer used to generate serial numbers
264 * for files.
265 *
266 * @return the serial number generator
267 */
268 public AtomicInteger getSerialNo() {
269 return serialNo;
270 }
271 }