/**
* Copyright 2011 meltmedia
*
* 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.xchain.framework.lifecycle;
import java.util.List;
import java.util.ListIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xchain.framework.osgi.OSGiCCLPolicy;
/**
* This class manages the lifecycle of threads that execute xchains code.
*
* @author Christian Trimble
* @author Josh Kennedy
* @author John Trimble
*/
@LifecycleClass(uri="http://www.xchain.org/thread")
public class ThreadLifecycle
{
/** The log for the thread lifecycle class. */
private Logger log = LoggerFactory.getLogger(ThreadLifecycle.class);
/** The static instance of this lifecycle class. */
private static ThreadLifecycle instance = new ThreadLifecycle();
/** Returns the ThreadLifecycle singleton. */
@LifecycleAccessor
public static ThreadLifecycle getInstance() { return instance; }
/** The thread local that holds context objects. */
private ThreadLocal<ThreadContext> contextTl = new ThreadLocal<ThreadContext>();
/** The list of thread steps that are called before a thread executes. */
private List<ThreadStep> threadStepList = null;
/** Strategy for setting the context class loader. */
private CCLPolicy cclPolicy = new NOPCCLPolicy();
/**
* This private constructor forces users of this class to access it through the singleton instance.
*/
private ThreadLifecycle() {
}
public boolean inThread()
{
return this.contextTl.get() != null;
}
/**
* Returns the current thread context.
*/
public ThreadContext getThreadContext()
{
return this.contextTl.get();
}
/**
* Returns the strategy used for setting the context class loader. By default, the context class loader is not altered.
* @return
*/
public CCLPolicy getCCLPolicy() {
return this.cclPolicy;
}
public void setCCLPolicy(CCLPolicy cclPolicy) {
this.cclPolicy = cclPolicy;
}
/**
* This mehtod must be called by a thread before interacting with xchains.
* @throws LifecycleException of the thread lifecycle could not be started. All thread related resources will be cleaned up
* before this exception is thrown. If this exception is thrown, stopThreadLifecycle should not be called.
*/
public void startThread(ThreadContext threadContext)
throws LifecycleException
{
this.getCCLPolicy().bindCCL();
// set the current context for the thread.
contextTl.set(threadContext);
// create the iterator for the steps.
ListIterator<ThreadStep> iterator = threadStepList.listIterator();
// iterate the steps.
try {
while( iterator.hasNext() ) {
ThreadStep step = iterator.next();
step.startThread(threadContext);
}
}
catch( Throwable t ) {
if( log.isDebugEnabled() ) {
log.debug("An exception was thrown while starting a thread context.", t);
}
iterator.previous();
while( iterator.hasPrevious() ) {
ThreadStep stopStep = iterator.previous();
try {
stopStep.stopThread(threadContext);
}
catch( Throwable t2 ) {
if( log.isWarnEnabled() ) {
log.warn("An error was thrown while cleaning up another exception in the ThreadLifecycle.", t2);
}
}
}
contextTl.remove();
this.getCCLPolicy().unbindCCL();
if( t instanceof LifecycleException ) {
throw (LifecycleException)t;
}
else {
throw new LifecycleException("An exception was thrown while starting a thread context.", t);
}
}
}
/**
* This method must be called by a thread after interacting with xchains.
*/
public void stopThread(ThreadContext threadContext)
throws LifecycleException
{
// iterate through the steps is reverse order, passing the thread context into each step.
ListIterator<ThreadStep> iterator = threadStepList.listIterator(threadStepList.size());
while( iterator.hasPrevious() ) {
ThreadStep stopStep = iterator.previous();
try {
stopStep.stopThread(threadContext);
}
catch( Throwable t ) {
if( log.isWarnEnabled() ) {
log.warn("An error was thrown while stopping a thread context.", t);
}
}
}
// remove the context.
contextTl.remove();
this.getCCLPolicy().unbindCCL();
}
@StartStep(localName="scan")
public void startLifecycle( LifecycleContext lifecycleContext )
throws LifecycleException
{
ThreadStepScanner scanner = new ThreadStepScanner( lifecycleContext );
try {
scanner.scan();
}
catch( Exception e ) {
if( log.isErrorEnabled() ) {
log.error("An exception was thrown while while scanning for thread step methods.", e);
}
throw new LifecycleException("An exception was thrown while scanning for thread step methods.", e);
}
threadStepList = scanner.getThreadStepList();
if( log.isInfoEnabled() ) {
StringBuilder message = new StringBuilder();
message.append("Found "+threadStepList.size()+" thread lifecycle steps.\n");
for( ThreadStep step : threadStepList ) {
message.append(" ").append(step.getQName().toString()).append("\n");
}
log.info(message.toString());
}
}
@StopStep(localName="scan")
public void stopLifecycle( LifecycleContext lifecycleContext )
{
threadStepList = null;
}
}