/* Initializer.java
*
* Copyright 2009-2012 Comcast Interactive Media, LLC.
*
* Licensed 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.fishwife.jrugged;
/**
* An {@link Initializer} allows a client to retry failed service initializations in the
* background. For example, the initial connection to a remote service may fail; the
* Initializer will take responsibility for continuing to retry that connection in a
* background thread (so that other services can try to initialize in the meantime). When
* initialization succeeds, the background thread terminates and the client service can
* enter normal operation.
* <p/>
* Sample usage:
*
* <pre>
* public class Service implements Initializable, Monitorable {
*
* // This status flag is set in afterInit() by the background
* // Initializer thread and must be volatile to ensure proper
* // cross thread state change notification
* private volatile Status status = Status.INIT;
*
* public Service() {
* // Allow the service/object to construct completely to avoid
* // potential memory, or incomplete object initializations
* // then call serviceInitialize() on this object
* }
*
* public void serviceInitialize() {
* new Initializer(this).initialize();
* }
*
* public void tryInit() throws Exception {
* // attempt an initialization here ...
* }
*
* public void afterInit() {
* status = Status.UP;
* }
*
* public Status getStatus() {
* return status;
* }
*
* public void aUsefulMethod(String arg1, int arg2) {
* // Always make sure the service is ready for use.
* if (status != Status.UP) {
* throw new IllegalStateException("Not yet initialized");
* }
*
* ... // Do something interesting now.
* }
* }
*
* </pre>
*/
public class Initializer implements Runnable {
/**
* By default, keep trying to initialize forever.
*/
private int maxRetries = Integer.MAX_VALUE;
/**
* Number of initialization attempts we have made.
*/
private int numAttempts = 0;
/**
* Retry an initialization every 60 seconds by default.
*/
private long retryMillis = 60 * 1000L;
/**
* This is the guy we're trying to initialize.
*/
private final Initializable client;
/**
* Current status.
*/
private boolean initialized = false;
/**
* Background initializer thread.
*/
private Thread thread;
/**
* Set this to true and interrupt thread to cleanly shutdown.
*/
private boolean cancelled = false;
public Initializer(final Initializable client) {
this.client = client;
}
/**
* Sets up the initialization retry process.
*/
public void initialize() {
thread = new Thread(this);
thread.start();
}
/**
* Shuts down the background retry process. If you are using the Spring framework, for
* example, if the client implements DisposableBean you can have the destroy() method
* of the client call this method to cleanly shutdown.
*/
public void destroy() {
cancelled = true;
if (thread != null) {
thread.interrupt();
}
}
@Override
public void run() {
while (!initialized && numAttempts < maxRetries && !cancelled) {
try {
numAttempts++;
client.tryInit();
initialized = true;
client.afterInit();
} catch (final Exception e) {
try {
Thread.sleep(retryMillis);
} catch (final InterruptedException ie) {
// nop
}
}
}
if (!initialized && numAttempts >= maxRetries && !cancelled) {
client.configuredRetriesMetOrExceededWithoutSuccess();
}
}
public boolean isInitialized() {
return initialized;
}
public boolean isCancelled() {
return cancelled;
}
public int getNumAttempts() {
return numAttempts;
}
public void setMaxRetries(final int n) {
maxRetries = n;
}
public void setRetryMillis(final long m) {
retryMillis = m;
}
}