[LANG-501] Added BackgroundInitializer class with JUnit tests.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@831586 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
86008e6c48
commit
8c413aecd8
|
@ -0,0 +1,331 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.commons.lang.concurrent;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* A class that allows complex initialization operations in a background task.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Applications often have to do some expensive initialization steps when they
|
||||||
|
* are started, e.g. constructing a connection to a database, reading a
|
||||||
|
* configuration file, etc. Doing these things in parallel can enhance
|
||||||
|
* performance as the CPU load can be improved. However, when access to the
|
||||||
|
* resources initialized in a background thread is actually required,
|
||||||
|
* synchronization has to be performed to ensure that their initialization is
|
||||||
|
* complete.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This abstract base class provides support for this use case. A concrete
|
||||||
|
* subclass must implement the {@link #initialize()} method. Here an arbitrary
|
||||||
|
* initialization can be implemented, and a result object can be returned. With
|
||||||
|
* this method in place the basic usage of this class is as follows (where
|
||||||
|
* {@code MyBackgroundInitializer} is a concrete subclass):
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* MyBackgroundInitializer initializer = new MyBackgroundInitializer();
|
||||||
|
* initializer.start();
|
||||||
|
* // Now do some other things. Initialization runs in a parallel thread
|
||||||
|
* ...
|
||||||
|
* // Wait for the end of initialization and access the result object
|
||||||
|
* Object result = initializer.get();
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* After the construction of a {@code BackgroundInitializer} object its
|
||||||
|
* {@link #start()} method has to be called. This starts the background
|
||||||
|
* processing. The application can now continue to do other things. When it
|
||||||
|
* needs access to the object produced by the {@code BackgroundInitializer} it
|
||||||
|
* calls its {@link #get()} method. If initialization is already complete,
|
||||||
|
* {@link #get()} returns the result object immediately. Otherwise it blocks
|
||||||
|
* until the result object is fully constructed.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* {@code BackgroundInitializer} is a thin wrapper around a {@code Future}
|
||||||
|
* object and uses an {@code ExecutorService} for running the background
|
||||||
|
* initialization task. It is possible to pass in an {@code ExecutorService} at
|
||||||
|
* construction time or set one using {@code setExternalExecutor()} before
|
||||||
|
* {@code start()} was called. Then this object is used to spawn the background
|
||||||
|
* task. If no {@code ExecutorService} has been provided, {@code
|
||||||
|
* BackgroundInitializer} creates a temporary {@code ExecutorService} and
|
||||||
|
* destroys it when initialization is complete.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* The methods provided by {@code BackgroundInitializer} provide for minimal
|
||||||
|
* interaction with the wrapped {@code Future} object. It is also possible to
|
||||||
|
* obtain the {@code Future} object directly. Then the enhanced functionality
|
||||||
|
* offered by {@code Future} can be used, e.g. to check whether the background
|
||||||
|
* operation is complete or to cancel the operation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @version $Id$
|
||||||
|
* @param <T> the type of the object managed by this initializer class
|
||||||
|
*/
|
||||||
|
public abstract class BackgroundInitializer<T> {
|
||||||
|
/** The external executor service for executing tasks. */
|
||||||
|
private ExecutorService externalExecutor;
|
||||||
|
|
||||||
|
/** A reference to the executor service that is actually used. */
|
||||||
|
private ExecutorService executor;
|
||||||
|
|
||||||
|
/** Stores the handle to the background task. */
|
||||||
|
private Future<T> future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@code BackgroundInitializer}. No external
|
||||||
|
* {@code ExecutorService} is used.
|
||||||
|
*/
|
||||||
|
protected BackgroundInitializer() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@code BackgroundInitializer} and initializes
|
||||||
|
* it with the given {@code ExecutorService}. If the {@code ExecutorService}
|
||||||
|
* is not null, the background task for initializing this object will be
|
||||||
|
* scheduled at this service. Otherwise a new temporary {@code
|
||||||
|
* ExecutorService} is created.
|
||||||
|
*
|
||||||
|
* @param exec an external {@code ExecutorService} to be used for task
|
||||||
|
* execution
|
||||||
|
*/
|
||||||
|
protected BackgroundInitializer(ExecutorService exec) {
|
||||||
|
setExternalExecutor(exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the external {@code ExecutorService} to be used by this class.
|
||||||
|
*
|
||||||
|
* @return the {@code ExecutorService}
|
||||||
|
*/
|
||||||
|
public final synchronized ExecutorService getExternalExecutor() {
|
||||||
|
return externalExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a flag whether this {@code BackgroundInitializer} has already
|
||||||
|
* been started.
|
||||||
|
*
|
||||||
|
* @return a flag whether the {@link #start()} method has already been
|
||||||
|
* called
|
||||||
|
*/
|
||||||
|
public synchronized boolean isStarted() {
|
||||||
|
return future != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an {@code ExecutorService} to be used by this class. The {@code
|
||||||
|
* ExecutorService} passed to this method is used for executing the
|
||||||
|
* background task. Thus it is possible to re-use an already existing
|
||||||
|
* {@code ExecutorService} or to use a specially configured one. If no
|
||||||
|
* {@code ExecutorService} is set, this instance creates a temporary one and
|
||||||
|
* destroys it after background initialization is complete. Note that this
|
||||||
|
* method must be called before {@link #start()}; otherwise an exception is
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* @param externalExecutor the {@code ExecutorService} to be used
|
||||||
|
* @throws IllegalStateException if this initializer has already been
|
||||||
|
* started
|
||||||
|
*/
|
||||||
|
public final synchronized void setExternalExecutor(
|
||||||
|
ExecutorService externalExecutor) {
|
||||||
|
if (isStarted()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot set ExecutorService after start()!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.externalExecutor = externalExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the background initialization. With this method the initializer
|
||||||
|
* becomes active and invokes the {@link #initialize()} method in a
|
||||||
|
* background task. A {@code BackgroundInitializer} can be started exactly
|
||||||
|
* once. The return value of this method determines whether the start was
|
||||||
|
* successful: only the first invocation of this method returns <b>true</b>,
|
||||||
|
* following invocations will return <b>false</b>.
|
||||||
|
*
|
||||||
|
* @return a flag whether the initializer could be started successfully
|
||||||
|
*/
|
||||||
|
public synchronized boolean start() {
|
||||||
|
// Not yet started?
|
||||||
|
if (!isStarted()) {
|
||||||
|
|
||||||
|
// Determine the executor to use and whether a temporary one has to
|
||||||
|
// be created
|
||||||
|
ExecutorService tempExec;
|
||||||
|
executor = getExternalExecutor();
|
||||||
|
if (executor == null) {
|
||||||
|
executor = tempExec = createExecutor();
|
||||||
|
} else {
|
||||||
|
tempExec = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
future = executor.submit(createTask(tempExec));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result of the background initialization. This method blocks
|
||||||
|
* until initialization is complete. If the background processing caused a
|
||||||
|
* runtime exception, it is directly thrown by this method. Checked
|
||||||
|
* exceptions, including {@code InterruptedException} are wrapped in a
|
||||||
|
* {@link ConcurrentException}. Calling this method before {@link #start()}
|
||||||
|
* was called causes an {@code IllegalStateException} exception to be
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* @return the object produced by this initializer
|
||||||
|
* @throws ConcurrentException if a checked exception occurred during
|
||||||
|
* background processing
|
||||||
|
* @throws IllegalStateException if {@link #start()} has not been called
|
||||||
|
*/
|
||||||
|
public T get() throws ConcurrentException {
|
||||||
|
try {
|
||||||
|
return getFuture().get();
|
||||||
|
} catch (ExecutionException execex) {
|
||||||
|
ConcurrentUtils.handleCause(execex);
|
||||||
|
return null; // should not be reached
|
||||||
|
} catch (InterruptedException iex) {
|
||||||
|
// reset interrupted state
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new ConcurrentException(iex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@code Future} object that was created when {@link #start()}
|
||||||
|
* was called. Therefore this method can only be called after {@code
|
||||||
|
* start()}.
|
||||||
|
*
|
||||||
|
* @return the {@code Future} object wrapped by this initializer
|
||||||
|
* @throws IllegalStateException if {@link #start()} has not been called
|
||||||
|
*/
|
||||||
|
public synchronized Future<T> getFuture() {
|
||||||
|
if (future == null) {
|
||||||
|
throw new IllegalStateException("start() must be called first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@code ExecutorService} that is actually used for executing
|
||||||
|
* the background task. This method can be called after {@link #start()}
|
||||||
|
* (before {@code start()} it returns <b>null</b>). If an external executor
|
||||||
|
* was set, this is also the active executor. Otherwise this method returns
|
||||||
|
* the temporary executor that was created by this object.
|
||||||
|
*
|
||||||
|
* @return the {@code ExecutorService} for executing the background task
|
||||||
|
*/
|
||||||
|
protected synchronized final ExecutorService getActiveExecutor() {
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of background tasks to be created for this
|
||||||
|
* initializer. This information is evaluated when a temporary {@code
|
||||||
|
* ExecutorService} is created. This base implementation returns 1. Derived
|
||||||
|
* classes that do more complex background processing can override it. This
|
||||||
|
* method is called from a synchronized block by the {@link #start()}
|
||||||
|
* method. Therefore overriding methods should be careful with obtaining
|
||||||
|
* other locks and return as fast as possible.
|
||||||
|
*
|
||||||
|
* @return the number of background tasks required by this initializer
|
||||||
|
*/
|
||||||
|
protected int getTaskCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the initialization. This method is called in a background task
|
||||||
|
* when this {@code BackgroundInitializer} is started. It must be
|
||||||
|
* implemented by a concrete subclass. An implementation is free to perform
|
||||||
|
* arbitrary initialization. The object returned by this method can be
|
||||||
|
* queried using the {@link #get()} method.
|
||||||
|
*
|
||||||
|
* @return a result object
|
||||||
|
* @throws Exception if an error occurs
|
||||||
|
*/
|
||||||
|
protected abstract T initialize() throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a task for the background initialization. The {@code Callable}
|
||||||
|
* object returned by this method is passed to the {@code ExecutorService}.
|
||||||
|
* This implementation returns a task that invokes the {@link #initialize()}
|
||||||
|
* method. If a temporary {@code ExecutorService} is used, it is destroyed
|
||||||
|
* at the end of the task.
|
||||||
|
*
|
||||||
|
* @param execDestory the {@code ExecutorService} to be destroyed by the
|
||||||
|
* task
|
||||||
|
* @return a task for the background initialization
|
||||||
|
*/
|
||||||
|
private Callable<T> createTask(ExecutorService execDestroy) {
|
||||||
|
return new InitializationTask(execDestroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the {@code ExecutorService} to be used. This method is called if
|
||||||
|
* no {@code ExecutorService} was provided at construction time.
|
||||||
|
*
|
||||||
|
* @return the {@code ExecutorService} to be used
|
||||||
|
*/
|
||||||
|
private ExecutorService createExecutor() {
|
||||||
|
return Executors.newFixedThreadPool(getTaskCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InitializationTask implements Callable<T> {
|
||||||
|
/** Stores the executor service to be destroyed at the end. */
|
||||||
|
private final ExecutorService executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@code InitializationTask} and initializes
|
||||||
|
* it with the {@code ExecutorService} to be destroyed at the end.
|
||||||
|
*
|
||||||
|
* @param exec the {@code ExecutorService}
|
||||||
|
*/
|
||||||
|
public InitializationTask(ExecutorService exec) {
|
||||||
|
executor = exec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates initialization and returns the result.
|
||||||
|
*
|
||||||
|
* @return the result object
|
||||||
|
* @throws Exception if an error occurs
|
||||||
|
*/
|
||||||
|
public T call() throws Exception {
|
||||||
|
try {
|
||||||
|
return initialize();
|
||||||
|
} finally {
|
||||||
|
if (executor != null) {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.commons.lang.concurrent;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
public class BackgroundInitializerTest extends TestCase {
|
||||||
|
/**
|
||||||
|
* Helper method for checking whether the initialize() method was correctly
|
||||||
|
* called. start() must already have been invoked.
|
||||||
|
*
|
||||||
|
* @param init the initializer to test
|
||||||
|
*/
|
||||||
|
private void checkInitialize(BackgroundInitializerTestImpl init) {
|
||||||
|
try {
|
||||||
|
Integer result = init.get();
|
||||||
|
assertEquals("Wrong result", 1, result.intValue());
|
||||||
|
assertEquals("Wrong number of invocations", 1, init.initializeCalls);
|
||||||
|
assertNotNull("No future", init.getFuture());
|
||||||
|
} catch (ConcurrentException cex) {
|
||||||
|
fail("Unexpected exception: " + cex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether initialize() is invoked.
|
||||||
|
*/
|
||||||
|
public void testInitialize() throws ConcurrentException {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
init.start();
|
||||||
|
checkInitialize(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to obtain the executor before start(). It should not have been
|
||||||
|
* initialized yet.
|
||||||
|
*/
|
||||||
|
public void testGetActiveExecutorBeforeStart() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
assertNull("Got an executor", init.getActiveExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an external executor is correctly detected.
|
||||||
|
*/
|
||||||
|
public void testGetActiveExecutorExternal() {
|
||||||
|
ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||||
|
try {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
|
||||||
|
exec);
|
||||||
|
init.start();
|
||||||
|
assertSame("Wrong executor", exec, init.getActiveExecutor());
|
||||||
|
checkInitialize(init);
|
||||||
|
} finally {
|
||||||
|
exec.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests getActiveExecutor() for a temporary executor.
|
||||||
|
*/
|
||||||
|
public void testGetActiveExecutorTemp() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
init.start();
|
||||||
|
assertNotNull("No active executor", init.getActiveExecutor());
|
||||||
|
checkInitialize(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the execution of the background task if a temporary executor has to
|
||||||
|
* be created.
|
||||||
|
*/
|
||||||
|
public void testInitializeTempExecutor() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
assertTrue("Wrong result of start()", init.start());
|
||||||
|
checkInitialize(init);
|
||||||
|
assertTrue("Executor not shutdown", init.getActiveExecutor()
|
||||||
|
.isShutdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an external executor can be set using the
|
||||||
|
* setExternalExecutor() method.
|
||||||
|
*/
|
||||||
|
public void testSetExternalExecutor() throws Exception {
|
||||||
|
ExecutorService exec = Executors.newCachedThreadPool();
|
||||||
|
try {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
init.setExternalExecutor(exec);
|
||||||
|
assertEquals("Wrong executor service", exec, init
|
||||||
|
.getExternalExecutor());
|
||||||
|
assertTrue("Wrong result of start()", init.start());
|
||||||
|
assertSame("Wrong active executor", exec, init.getActiveExecutor());
|
||||||
|
checkInitialize(init);
|
||||||
|
assertFalse("Executor was shutdown", exec.isShutdown());
|
||||||
|
} finally {
|
||||||
|
exec.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that setting an executor after start() causes an exception.
|
||||||
|
*/
|
||||||
|
public void testSetExternalExecutorAfterStart() throws ConcurrentException {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
init.start();
|
||||||
|
try {
|
||||||
|
init.setExternalExecutor(Executors.newSingleThreadExecutor());
|
||||||
|
fail("Could set executor after start()!");
|
||||||
|
} catch (IllegalStateException istex) {
|
||||||
|
init.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests invoking start() multiple times. Only the first invocation should
|
||||||
|
* have an effect.
|
||||||
|
*/
|
||||||
|
public void testStartMultipleTimes() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
assertTrue("Wrong result for start()", init.start());
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertFalse("Could start again", init.start());
|
||||||
|
}
|
||||||
|
checkInitialize(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests calling get() before start(). This should cause an exception.
|
||||||
|
*/
|
||||||
|
public void testGetBeforeStart() throws ConcurrentException {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
try {
|
||||||
|
init.get();
|
||||||
|
fail("Could call get() before start()!");
|
||||||
|
} catch (IllegalStateException istex) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the get() method if background processing causes a runtime
|
||||||
|
* exception.
|
||||||
|
*/
|
||||||
|
public void testGetRuntimeException() throws Exception {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
RuntimeException rex = new RuntimeException();
|
||||||
|
init.ex = rex;
|
||||||
|
init.start();
|
||||||
|
try {
|
||||||
|
init.get();
|
||||||
|
fail("Exception not thrown!");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
assertEquals("Runtime exception not thrown", rex, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the get() method if background processing causes a checked
|
||||||
|
* exception.
|
||||||
|
*/
|
||||||
|
public void testGetCheckedException() throws Exception {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
Exception ex = new Exception();
|
||||||
|
init.ex = ex;
|
||||||
|
init.start();
|
||||||
|
try {
|
||||||
|
init.get();
|
||||||
|
fail("Exception not thrown!");
|
||||||
|
} catch (ConcurrentException cex) {
|
||||||
|
assertEquals("Exception not thrown", ex, cex.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the get() method if waiting for the initialization is interrupted.
|
||||||
|
*/
|
||||||
|
public void testGetInterruptedException() throws Exception {
|
||||||
|
ExecutorService exec = Executors.newSingleThreadExecutor();
|
||||||
|
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl(
|
||||||
|
exec);
|
||||||
|
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||||
|
init.shouldSleep = true;
|
||||||
|
init.start();
|
||||||
|
final AtomicReference<InterruptedException> iex = new AtomicReference<InterruptedException>();
|
||||||
|
Thread getThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
init.get();
|
||||||
|
} catch (ConcurrentException cex) {
|
||||||
|
if (cex.getCause() instanceof InterruptedException) {
|
||||||
|
iex.set((InterruptedException) cex.getCause());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
assertTrue("Thread not interrupted", isInterrupted());
|
||||||
|
latch1.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getThread.start();
|
||||||
|
getThread.interrupt();
|
||||||
|
latch1.await();
|
||||||
|
exec.shutdownNow();
|
||||||
|
exec.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull("No interrupted exception", iex.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests isStarted() before start() was called.
|
||||||
|
*/
|
||||||
|
public void testIsStartedFalse() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
assertFalse("Already started", init.isStarted());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests isStarted() after start().
|
||||||
|
*/
|
||||||
|
public void testIsStartedTrue() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
init.start();
|
||||||
|
assertTrue("Not started", init.isStarted());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests isStarted() after the background task has finished.
|
||||||
|
*/
|
||||||
|
public void testIsStartedAfterGet() {
|
||||||
|
BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||||
|
init.start();
|
||||||
|
checkInitialize(init);
|
||||||
|
assertTrue("Not started", init.isStarted());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A concrete implementation of BackgroundInitializer. It also overloads
|
||||||
|
* some methods that simplify testing.
|
||||||
|
*/
|
||||||
|
private static class BackgroundInitializerTestImpl extends
|
||||||
|
BackgroundInitializer<Integer> {
|
||||||
|
/** An exception to be thrown by initialize(). */
|
||||||
|
Exception ex;
|
||||||
|
|
||||||
|
/** A flag whether the background task should sleep a while. */
|
||||||
|
boolean shouldSleep;
|
||||||
|
|
||||||
|
/** The number of invocations of initialize(). */
|
||||||
|
volatile int initializeCalls;
|
||||||
|
|
||||||
|
public BackgroundInitializerTestImpl() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackgroundInitializerTestImpl(ExecutorService exec) {
|
||||||
|
super(exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records this invocation. Optionally throws an exception or sleeps a
|
||||||
|
* while.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Integer initialize() throws Exception {
|
||||||
|
if (ex != null) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
if (shouldSleep) {
|
||||||
|
Thread.sleep(60000L);
|
||||||
|
}
|
||||||
|
return ++initializeCalls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue