/*
* Copyright 2012-2015, the original author or authors.
*
* 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 com.flipkart.phantom.task.spi.registry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.trpr.platform.core.PlatformException;
import org.trpr.platform.core.impl.logging.LogFactory;
import org.trpr.platform.core.spi.logging.Logger;
import com.flipkart.phantom.task.impl.TaskHandler;
import com.flipkart.phantom.task.spi.AbstractHandler;
import com.flipkart.phantom.task.spi.TaskContext;
import com.github.kristofa.brave.TraceFilter;
/**
* Interface for handler registry. Controls lifecycle methods of all handlers understood by the registry.
*
* @author kartikbu
* @version 1.0
* @created 30/7/13 12:43 AM
*/
public abstract class AbstractHandlerRegistry<T extends AbstractHandler> {
/** Logger for this class*/
private static final Logger LOGGER = LogFactory.getLogger(AbstractHandlerRegistry.class);
/** Default value for handler init concurrency */
private static final int DEFAULT_HANDLER_INIT_CONCURRENCY = 5;
/** The handler init concurrency */
private int handlerInitConcurrency = AbstractHandlerRegistry.DEFAULT_HANDLER_INIT_CONCURRENCY;
/** Map of AbstractHandlerS keyed by their names */
protected Map<String,T> handlers = new HashMap<String,T>();
/** Map of TraceFilterS keyed by the Handler names */
protected Map<String,TraceFilter> traceFilters = new HashMap<String,TraceFilter>();
/**
* Lifecycle init method. Initializes all individual handlers understood.
* @param handlerConfigInfoList List of HandlerConfigInfo which is to be analyzed and initialized
* @param taskContext The task context object
* @return array of AbstractHandlerRegistry.InitedHandlerInfo instances for each inited handler
* @throws Exception
*/
@SuppressWarnings("unchecked")
public AbstractHandlerRegistry.InitedHandlerInfo<T>[] init(List<HandlerConfigInfo> handlerConfigInfoList, TaskContext taskContext) throws Exception {
// this is a synchronized list as multiple threads add to it and we also iterate through it
List<AbstractHandlerRegistry.InitedHandlerInfo<T>> initedHandlerInfos = Collections.synchronizedList(new LinkedList<AbstractHandlerRegistry.InitedHandlerInfo<T>>());
// we want to init handlers defined as HandlerConfigInfo#FIRST_ORDER first, serially, before loading others in parallel
// create a new list as we are changing ordering of elements being passed in
List<HandlerConfigInfo> tempHandlerConfigInfoList = new LinkedList<HandlerConfigInfo>();
tempHandlerConfigInfoList.addAll(handlerConfigInfoList);
Collections.sort(tempHandlerConfigInfoList, new Comparator<HandlerConfigInfo>() {
public int compare(HandlerConfigInfo o1, HandlerConfigInfo o2) {
// sort by ascending order of HandlerConfigInfo#getLoadOrder()
return (o1.getLoadOrder() - o2.getLoadOrder());
}
});
for (int i=0; i<tempHandlerConfigInfoList.size(); i++) {
HandlerConfigInfo handlerConfigInfo = tempHandlerConfigInfoList.get(i);
if (handlerConfigInfo.getLoadOrder() == HandlerConfigInfo.FIRST_ORDER) {
this.initHandlers(initedHandlerInfos, taskContext, 1, handlerConfigInfo); // we load this serially
} else {
this.initHandlers(initedHandlerInfos, taskContext, this.getHandlerInitConcurrency(),
tempHandlerConfigInfoList.subList(i, tempHandlerConfigInfoList.size()).toArray(new HandlerConfigInfo[0])); // load all remaining handlers in parallel
break;
}
}
return initedHandlerInfos.toArray(new AbstractHandlerRegistry.InitedHandlerInfo[0]);
}
/**
* Method to reinitialize a handler.
* @param name Name of the handler.
* @param taskContext task context object
* @throws Exception
*/
public void reinitHandler(String name, TaskContext taskContext) throws Exception {
T handler = this.handlers.get(name);
if (handler != null) {
try {
handler.deactivate();
handler.shutdown(taskContext);
handler.init(taskContext);
handler.activate();
} catch (Exception e) {
LOGGER.error("Error initializing " + this.getHandlerType().getName() + " : {}. Error is: " + e.getMessage(), handler.getName(), e);
throw new PlatformException("Error reinitialising " + this.getHandlerType().getName() + " : " + handler.getName(), e);
}
}
}
/**
* Lifecycle shutdown method. Shuts down all individual handlers understood.
* @param taskContext The task context object
* @throws Exception
*/
public void shutdown(TaskContext taskContext) throws Exception {
Iterator<String> iterator = this.handlers.keySet().iterator();
while (iterator.hasNext()) {
String name = iterator.next();
LOGGER.info("Shutting down {}: " + name, this.getHandlerType().getName());
try {
this.handlers.get(name).shutdown(taskContext);
this.handlers.get(name).deactivate();
this.traceFilters.remove(name);
this.postUnregisterHandler(this.handlers.get(name));
iterator.remove();
} catch (Exception e) {
LOGGER.warn("Failed to shutdown {}: " + name, this.getHandlerType().getName(), e);
}
}
}
/**
* Enumeration method for all handlers. Returns a List of AbstractHandler instances
* @return List
*/
public List<T> getHandlers() {
return new ArrayList<T>(this.handlers.values());
}
/**
* Get a handler given name
* @param name String name of the handler
* @return AbstractHandler
*/
public T getHandler(String name) {
return this.handlers.get(name);
}
/**
* Gets the TraceFilter associated with the handler identified by the specified name
* @param handlerName the TaskHandler name
* @return TraceFilter instance for the TaskHandler
*/
public TraceFilter getTraceFilterForHandler(String handlerName) {
return this.traceFilters.get(handlerName);
}
/**
* Unregisters (removes) a AbstractHandler from this registry.
* @param handler the AbstractHandler to be removed
*/
public void unregisterTaskHandler(T handler) {
this.handlers.remove(handler.getName());
this.traceFilters.remove(handler.getName());
this.postUnregisterHandler(handler);
};
/**
* Returns the {@link AbstractHandler} type that this registry manages
* @return the AbstractHandler type
*/
protected abstract Class<T> getHandlerType();
/**
* Callback method after initing handler. Subtypes may override to perform custom post init operations
* @param handler the AbstractHandler that was inited
*/
protected void postInitHandler(T handler) {
// no op
}
/**
* Callback method after unregistering handler. Subtypes may override to perform custom post unregister operations
* @param handler the AbstractHandler that was unregistered
*/
protected void postUnregisterHandler(T handler) {
// no op
}
/**
* Container object for inited handlers and the respective configuration
*/
public static final class InitedHandlerInfo<T extends AbstractHandler> {
private T initedHandler;
private HandlerConfigInfo handlerConfigInfo;
public InitedHandlerInfo(T initedHandler, HandlerConfigInfo handlerConfigInfo) {
this.initedHandler = initedHandler;
this.handlerConfigInfo = handlerConfigInfo;
}
public T getInitedHandler() {
return initedHandler;
}
public HandlerConfigInfo getHandlerConfigInfo() {
return handlerConfigInfo;
}
}
/**
* Helper method to init handlers with the specified concurrency
* @param initedHandlerInfos the list to add meta data of all successfully inited handlers
* @param taskContext TaskContext instance to pass to all handlers during init
* @param concurrency the concurrent number of handler inits
* @param configInfos HandlerConfigInfo instances containing handlers to be inited
*/
@SuppressWarnings("unchecked")
private void initHandlers(List<AbstractHandlerRegistry.InitedHandlerInfo<T>> initedHandlerInfos, TaskContext taskContext,
int concurrency, HandlerConfigInfo...configInfos) {
ExecutorService pool = Executors.newFixedThreadPool(concurrency,new ThreadFactory() {
int nameSuffix = -1;
public Thread newThread(Runnable r) {
nameSuffix += 1;
return new Thread(r,"Handler-Init-Thread-" + nameSuffix);
}
});
List<FutureTask<T>> handlerInitTasks = new LinkedList<FutureTask<T>>();
for (HandlerConfigInfo handlerConfigInfo : configInfos) {
String[] handlerBeanIds = handlerConfigInfo.getProxyHandlerContext().getBeanNamesForType(this.getHandlerType());
for (String taskHandlerBeanId : handlerBeanIds) {
T handler = (T) handlerConfigInfo.getProxyHandlerContext().getBean(taskHandlerBeanId);
handler.setVersion(handlerConfigInfo.getVersion());
FutureTask<T> handlerInitTask = new FutureTask<T>(new HandlerInitFutureTask(handler,taskContext,handlerConfigInfo,initedHandlerInfos));
handlerInitTasks.add(handlerInitTask);
pool.execute(handlerInitTask);
}
}
for (FutureTask<T> handlerInitTask : handlerInitTasks) {
T initedHandler = null;
try {
initedHandler = handlerInitTask.get();
} catch (Exception e) {
LOGGER.error("Error initializing handlers of type : " + getHandlerType().getName() + ".Error is: " + e.getMessage(), e);
pool.shutdownNow();
throw new PlatformException("Error initializing handlers of type : " + getHandlerType().getName() + ".Error is: " + e.getMessage(), e);
}
if (!initedHandler.isActive()) {
if (initedHandler.getInitOutcomeStatus() == AbstractHandler.VETO_INIT) {
pool.shutdownNow();
throw new PlatformException("Error initializing vetoing handler " + getHandlerType().getName() + " : " + initedHandler.getName());
} else {
LOGGER.warn("Continuing after init failed for non-vetoing handler " + getHandlerType().getName() + " : " + initedHandler.getName());
}
}
}
LOGGER.info("Number of handlers inited : " + handlerInitTasks.size());
while(!pool.isTerminated()) {
pool.shutdown();
try {
pool.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// we dont expect this to happen, but if it does, throw a Platform exception
throw new PlatformException("Error initializing handlers. Init interrupted. Error is : " + e.getMessage(), e);
}
}
}
/**
* {@link Callable} implementation for initing {@link TaskHandler} instances asynchronously using {@link Future}
*/
private class HandlerInitFutureTask implements Callable<T> {
T handler;
TaskContext taskContext;
HandlerConfigInfo handlerConfigInfo;
List<AbstractHandlerRegistry.InitedHandlerInfo<T>> initedHandlerInfos;
HandlerInitFutureTask(T handler,TaskContext taskContext,HandlerConfigInfo handlerConfigInfo,List<AbstractHandlerRegistry.InitedHandlerInfo<T>> initedHandlerInfos) {
this.handler = handler;
this.taskContext = taskContext;
this.handlerConfigInfo = handlerConfigInfo;
this.initedHandlerInfos = initedHandlerInfos;
}
public T call() throws Exception {
try {
if (!handler.isActive()) { // init the handler only if not inited already
LOGGER.info("Initializing {} : " + handler.getName(), getHandlerType().getName());
handler.init(taskContext);
// call post init for any registry specific handling
postInitHandler(handler);
handler.activate();
initedHandlerInfos.add(new AbstractHandlerRegistry.InitedHandlerInfo<T>(handler,handlerConfigInfo));
// put in all handlers map
handlers.put(handler.getName(),handler);
// add the trace filter for the handler
traceFilters.put(handler.getName(), handler.getTraceFilter());
}
} catch (Exception e) {
LOGGER.error("Error initializing " + getHandlerType().getName() + " : {}. Error is: " + e.getMessage(), handler.getName(), e);
// consuming the exception here. Failures will be handled in Future#get()
}
return handler;
}
}
/** Getter/Setter methods */
public int getHandlerInitConcurrency() {
return handlerInitConcurrency;
}
public void setHandlerInitConcurrency(int handlerInitConcurrency) {
this.handlerInitConcurrency = handlerInitConcurrency;
}
}