com.flipkart.phantom.runtime.impl.spring.ServiceProxyComponentContainer.java Source code

Java tutorial

Introduction

Here is the source code for com.flipkart.phantom.runtime.impl.spring.ServiceProxyComponentContainer.java

Source

/*
 * 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.runtime.impl.spring;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.trpr.platform.core.PlatformException;
import org.trpr.platform.core.impl.logging.LogFactory;
import org.trpr.platform.core.spi.event.PlatformEventProducer;
import org.trpr.platform.core.spi.logging.Logger;
import org.trpr.platform.model.event.PlatformEvent;
import org.trpr.platform.runtime.common.RuntimeConstants;
import org.trpr.platform.runtime.common.RuntimeVariables;
import org.trpr.platform.runtime.impl.bootstrapext.spring.ApplicationContextFactory;
import org.trpr.platform.runtime.impl.config.FileLocator;
import org.trpr.platform.runtime.spi.bootstrapext.BootstrapExtension;
import org.trpr.platform.runtime.spi.component.ComponentContainer;

import com.flipkart.phantom.runtime.ServiceProxyFrameworkConstants;
import com.flipkart.phantom.runtime.impl.hystrix.HystrixEventReceiver;
import com.flipkart.phantom.runtime.impl.server.AbstractNetworkServer;
import com.flipkart.phantom.runtime.impl.spring.admin.SPConfigServiceImpl;
import com.flipkart.phantom.runtime.spi.spring.admin.SPConfigService;
import com.flipkart.phantom.task.spi.AbstractHandler;
import com.flipkart.phantom.task.spi.TaskContext;
import com.flipkart.phantom.task.spi.registry.AbstractHandlerRegistry;
import com.flipkart.phantom.task.spi.registry.HandlerConfigInfo;
import com.netflix.hystrix.Hystrix;
import com.netflix.hystrix.strategy.HystrixPlugins;

/**
 * The <code>ServiceProxyComponentContainer</code> class is a ComponentContainer implementation as defined by Trooper {@link "https://github.com/regunathb/Trooper"}
 * that starts up a runtime for proxying service requests.
 * This container loads the service proxy listener from {@link ServiceProxyFrameworkConstants#SPRING_PROXY_LISTENER_CONFIG}.
 * It then locates and loads all service proxy handlers contained in files named by the value of {@link ServiceProxyFrameworkConstants#SPRING_PROXY_HANDLER_CONFIG}.
 * This container also loads all common proxy related Spring beans contained in {@link ServiceProxyFrameworkConstants#COMMON_PROXY_CONFIG} and
 * ensures that all beans declared in Trooper ServerConstants.COMMON_SPRING_BEANS_CONFIG are available to the proxy handler beans by
 * specifying the common beans context as the parent for each proxy app context created by this container.
 *
 * @see org.trpr.platform.runtime.spi.component.ComponentContainer
 * @author Regunath B
 * @version 1.0, 14 Mar 2013
 */
public class ServiceProxyComponentContainer implements ComponentContainer {

    /**
     * The default Event producer bean name
     */
    private static final String DEFAULT_EVENT_PRODUCER = "platformEventProducer";

    /** The prefix to be added to file absolute paths when loading Spring XMLs using the FileSystemXmlApplicationContext */
    private static final String FILE_PREFIX = "file:";

    /** The bean names of the service proxy framework classes initialized by this container */
    private static final String CONFIG_SERVICE_BEAN = "configService";
    private static final String TASK_CONTEXT_BEAN = "taskContext";

    /** Logger for this class */
    private static final Logger LOGGER = LogFactory.getLogger(ServiceProxyComponentContainer.class);

    /** The common proxy handler beans context */
    private static AbstractApplicationContext commonProxyHandlerBeansContext;

    /** The list of HandlerConfigInfo holding all proxy handler instances loaded by this container */
    private List<HandlerConfigInfo> handlerConfigInfoList = new LinkedList<HandlerConfigInfo>();

    /** Local reference for all BootstrapExtensionS loaded by the Container and set on this ComponentContainer */
    private BootstrapExtension[] loadedBootstrapExtensions;

    /** The Thread's context class loader that is used in on the fly loading of proxy handler definitions */
    private ClassLoader tccl;

    /** The list of registered registries */
    private List<AbstractHandlerRegistry> registries = new ArrayList<AbstractHandlerRegistry>();

    /** The configService instance */
    private SPConfigService configService;

    /** The TaskContext bean instance */
    private TaskContext taskContext;

    /**
     * Returns the common Proxy Handler Spring beans application context that is intended as parent of all proxy handler application contexts
     * WARN : this method can return null if this ComponentContainer is not suitably initialized via a call to {@link #init()}
     * @return null or the common proxy handler AbstractApplicationContext
     */
    public static AbstractApplicationContext getCommonProxyHandlerBeansContext() {
        return ServiceProxyComponentContainer.commonProxyHandlerBeansContext;
    }

    /**
     * Interface method implementation. Returns the fully qualified class name of this class
     * @see org.trpr.platform.runtime.spi.component.ComponentContainer#getName()
     */
    public String getName() {
        return this.getClass().getName();
    }

    /**
     * Interface method implementation. Stores local references to the specified BootstrapExtension instances.
     * @see org.trpr.platform.runtime.spi.component.ComponentContainer#setLoadedBootstrapExtensions(org.trpr.platform.runtime.spi.bootstrapext.BootstrapExtension[])
     */
    public void setLoadedBootstrapExtensions(BootstrapExtension... bootstrapExtensions) {
        this.loadedBootstrapExtensions = bootstrapExtensions;
    }

    /**
     * Interface method implementation. Locates and loads all configured proxy handlers.
     * @see ComponentContainer#init()
     */
    public void init() throws PlatformException {
        //Register HystrixEventNotifier
        if (HystrixPlugins.getInstance().getEventNotifier() == null) {
            HystrixPlugins.getInstance().registerEventNotifier(new HystrixEventReceiver());
        }
        // store the thread's context class loader for later use in on the fly loading of proxy handler app contexts
        this.tccl = Thread.currentThread().getContextClassLoader();

        // The common proxy handler beans context is loaded first using the Platform common beans context as parent
        // load this from classpath as it is packaged with the binaries
        ApplicationContextFactory defaultCtxFactory = null;
        for (BootstrapExtension be : this.loadedBootstrapExtensions) {
            if (ApplicationContextFactory.class.isAssignableFrom(be.getClass())) {
                defaultCtxFactory = (ApplicationContextFactory) be;
                break;
            }
        }

        ServiceProxyComponentContainer.commonProxyHandlerBeansContext = new ClassPathXmlApplicationContext(
                new String[] { ServiceProxyFrameworkConstants.COMMON_PROXY_CONFIG },
                defaultCtxFactory.getCommonBeansContext());
        // add the common proxy beans independently to the list of proxy handler contexts as common handlers are declared there
        this.handlerConfigInfoList
                .add(new HandlerConfigInfo(new File(ServiceProxyFrameworkConstants.COMMON_PROXY_CONFIG), null,
                        ServiceProxyComponentContainer.commonProxyHandlerBeansContext));

        // Get the Config Service Bean
        this.configService = (SPConfigServiceImpl) ServiceProxyComponentContainer.commonProxyHandlerBeansContext
                .getBean(ServiceProxyComponentContainer.CONFIG_SERVICE_BEAN);
        ((SPConfigServiceImpl) this.configService).setComponentContainer(this);

        // Load additional if runtime nature is "server". This context is the new common beans context
        if (RuntimeVariables.getRuntimeNature().equalsIgnoreCase(RuntimeConstants.SERVER)) {
            ServiceProxyComponentContainer.commonProxyHandlerBeansContext = new ClassPathXmlApplicationContext(
                    new String[] { ServiceProxyFrameworkConstants.COMMON_PROXY_SERVER_NATURE_CONFIG },
                    ServiceProxyComponentContainer.getCommonProxyHandlerBeansContext());
            // now add the common server nature proxy hander beans to the contexts list
            this.handlerConfigInfoList.add(new HandlerConfigInfo(
                    new File(ServiceProxyFrameworkConstants.COMMON_PROXY_SERVER_NATURE_CONFIG), null,
                    ServiceProxyComponentContainer.getCommonProxyHandlerBeansContext()));
        }

        // locate and load a single common proxy handler bean XML files which is initialized before all other individual handlers
        // in case multiple are found fail the bootstrap.
        File[] commonProxyHandlerConfigFiles = FileLocator
                .findFiles(ServiceProxyFrameworkConstants.COMMON_PROXY_HANDLER_CONFIG);
        if (commonProxyHandlerConfigFiles.length > 0) {
            if (commonProxyHandlerConfigFiles.length == 1) {
                File commonProxyHandlerConfigFile = commonProxyHandlerConfigFiles[0];
                // load the common proxy handler
                this.loadProxyHandlerContext(new HandlerConfigInfo(commonProxyHandlerConfigFile));
                LOGGER.info("Loaded Common Proxy Handler Config: " + commonProxyHandlerConfigFile);
            } else {
                final String errorMessage = "Found multiple common-proxy-handler-configs, only one is allowed";
                LOGGER.error(errorMessage);
                for (File commonHandlerConfig : commonProxyHandlerConfigFiles)
                    LOGGER.error(commonHandlerConfig.getAbsolutePath());
                throw new PlatformException(errorMessage);
            }
        }

        // locate and load the individual proxy handler bean XML files using the common proxy handler beans context as parent
        File[] proxyHandlerBeansFiles = FileLocator
                .findFiles(ServiceProxyFrameworkConstants.SPRING_PROXY_HANDLER_CONFIG);
        for (File proxyHandlerBeansFile : proxyHandlerBeansFiles) {
            HandlerConfigInfo handlerConfigInfo = new HandlerConfigInfo(proxyHandlerBeansFile);
            // load the proxy handler's appcontext
            this.loadProxyHandlerContext(handlerConfigInfo);
            LOGGER.info("Loaded: " + proxyHandlerBeansFile);
        }

        // add the proxy listener beans to the contexts list (these have the thrift handlers)
        File[] proxyListenerBeanFiles = FileLocator
                .findFiles(ServiceProxyFrameworkConstants.SPRING_PROXY_LISTENER_CONFIG);
        for (File proxyListenerBeanFile : proxyListenerBeanFiles) {
            // locate and load the service proxy listener defined in the file identified by {@link ServiceProxyFrameworkConstants#SPRING_PROXY_LISTENER_CONFIG}
            AbstractApplicationContext listenerContext = new FileSystemXmlApplicationContext(
                    new String[] { FILE_PREFIX + proxyListenerBeanFile.getAbsolutePath() },
                    ServiceProxyComponentContainer.getCommonProxyHandlerBeansContext());
            this.handlerConfigInfoList.add(new HandlerConfigInfo(proxyListenerBeanFile, null, listenerContext));
            LOGGER.info("Loaded: " + proxyListenerBeanFile);
        }

        // load all registries
        for (HandlerConfigInfo handlerConfigInfo : handlerConfigInfoList) {
            // handler registries
            String[] registryBeans = handlerConfigInfo.getProxyHandlerContext()
                    .getBeanNamesForType(AbstractHandlerRegistry.class);
            for (String registryBean : registryBeans) {
                AbstractHandlerRegistry registry = (AbstractHandlerRegistry) handlerConfigInfo
                        .getProxyHandlerContext().getBean(registryBean);
                LOGGER.info("Found handler registry: " + registry.getClass().getName());
                // init the Registry
                try {
                    this.taskContext = (TaskContext) ServiceProxyComponentContainer
                            .getCommonProxyHandlerBeansContext()
                            .getBean(ServiceProxyComponentContainer.TASK_CONTEXT_BEAN);
                    AbstractHandlerRegistry.InitedHandlerInfo[] initedHandlerInfos = registry
                            .init(this.handlerConfigInfoList, this.taskContext);
                    LOGGER.info("Initialized handler registry: " + registry.getClass().getName());
                    //Add the file path of each inited handler to SPConfigService (for configuration console)
                    for (AbstractHandlerRegistry.InitedHandlerInfo initedHandlerInfo : initedHandlerInfos) {
                        this.configService.addHandlerConfigPath(
                                initedHandlerInfo.getHandlerConfigInfo().getXmlConfigFile(),
                                initedHandlerInfo.getInitedHandler());
                    }
                } catch (Exception e) {
                    LOGGER.error("Error initializing registry: " + registry.getClass().getName());
                    throw new PlatformException("Error initializing registry: " + registry.getClass().getName(), e);
                }
                // add registry to config
                this.configService.addHandlerRegistry(registry);
                // add registry to local list
                this.registries.add(registry);
            }

            // add all network servers to config
            String[] networkServerBeans = handlerConfigInfo.getProxyHandlerContext()
                    .getBeanNamesForType(AbstractNetworkServer.class);
            for (String networkServerBean : networkServerBeans) {
                AbstractNetworkServer networkServer = (AbstractNetworkServer) handlerConfigInfo
                        .getProxyHandlerContext().getBean(networkServerBean);
                // init the server
                try {
                    networkServer.init();
                } catch (Exception e) {
                    LOGGER.error("Error initializeing network server: " + networkServer.getServerType() + ": "
                            + networkServer.getServerEndpoint());
                    throw new PlatformException("Error initializeing network server: "
                            + networkServer.getServerType() + ": " + networkServer.getServerEndpoint(), e);
                }
                configService.addDeployedNetworkServer(networkServer);
            }

        }
    }

    /**
     * Interface method implementation. Destroys the Spring application context containing loaded proxy handler definitions.
     * @see ComponentContainer#destroy()
     */
    public void destroy() throws PlatformException {
        // reset the Hystrix instance
        Hystrix.reset();
        // now shutdown all task handlers
        for (AbstractHandlerRegistry registry : registries) {
            try {
                registry.shutdown(taskContext);
            } catch (Exception e) {
                LOGGER.warn("Error shutting down registry: " + registry.getClass().getName());
            }
        }
        // finally close the context
        for (HandlerConfigInfo handlerConfigInfo : this.handlerConfigInfoList) {
            handlerConfigInfo.getProxyHandlerContext().close();
        }
        this.handlerConfigInfoList = null;
    }

    /**
     * Interface method implementation. Publishes the specified event to using a named bean DEFAULT_EVENT_PRODUCER looked up from the
     * common proxy handler context (i.e. ServiceProxyFrameworkConstants.COMMON_PROXY_CONFIG).
     * Note that typically no consumers are registered when running this container
     */
    public void publishEvent(PlatformEvent event) {
        PlatformEventProducer publisher = (PlatformEventProducer) ServiceProxyComponentContainer.commonProxyHandlerBeansContext
                .getBean(DEFAULT_EVENT_PRODUCER);
        publisher.publishEvent(event);
    }

    /**
     * Interface method implementation. Publishes the specified event using the {@link #publishEvent(PlatformEvent)} method
     * @see ComponentContainer#publishBootstrapEvent(PlatformEvent)
     */
    public void publishBootstrapEvent(PlatformEvent bootstrapEvent) {
        this.publishEvent(bootstrapEvent);
    }

    /**
     * Reloads and re-initalizes the specified handler. The new definition is loaded from the specified Reosurce location
     * @param handler  the AbstractHandler to be de-registered
     * @param resource the location to load the new definition of the handler from
     */
    public void reloadHandler(AbstractHandler handler, Resource resource) {
        AbstractHandlerRegistry registry = this.getRegistry(handler.getName());
        registry.unregisterTaskHandler(handler);
        LOGGER.debug("Unregistered TaskHandler: " + handler.getName());
        this.loadComponent(resource);
        // now add the newly loaded handler to its registry
        for (HandlerConfigInfo handlerConfigInfo : this.handlerConfigInfoList) {
            if (handlerConfigInfo.getXmlConfigFile().getAbsolutePath()
                    .equalsIgnoreCase(((FileSystemResource) resource).getFile().getAbsolutePath())) {
                List<HandlerConfigInfo> reloadHandlerConfigInfoList = new LinkedList<HandlerConfigInfo>();
                reloadHandlerConfigInfoList.add(handlerConfigInfo);
                try {
                    registry.init(reloadHandlerConfigInfoList, taskContext);
                } catch (Exception e) {
                    LOGGER.error("Error updating registry : " + registry.getClass().getName() + " for handler : "
                            + handler.getName(), e);
                    throw new PlatformException("Error updating registry : " + registry.getClass().getName()
                            + " for handler : " + handler.getName(), e);
                }
                return;
            }
        }
    }

    /**
     * Interface method implementation. Loads/Reloads proxy handler(s) defined in the specified {@link FileSystemResource}
     * @see org.trpr.platform.runtime.spi.component.ComponentContainer#loadComponent(org.springframework.core.io.Resource)
     */
    public void loadComponent(Resource resource) {
        if (!FileSystemResource.class.isAssignableFrom(resource.getClass()) || !resource.getFilename()
                .equalsIgnoreCase(ServiceProxyFrameworkConstants.SPRING_PROXY_HANDLER_CONFIG)) {
            throw new UnsupportedOperationException("Proxy handers can be loaded only from files by name : "
                    + ServiceProxyFrameworkConstants.SPRING_PROXY_HANDLER_CONFIG + ". Specified resource is : "
                    + resource.toString());
        }
        loadProxyHandlerContext(new HandlerConfigInfo(((FileSystemResource) resource).getFile()));
    }

    /**
     * Returns the AbstractHandlerRegistry in which an AbstractHandler identified by the specified name has been registered
     * @param handlerName the AbstractHandler name
     * @return AbstractHandlerRegistry where the handler is registered
     * @throws UnsupportedOperationException if a registry is not found
     */
    public AbstractHandlerRegistry getRegistry(String handlerName) {
        for (AbstractHandlerRegistry registry : this.registries) {
            if (registry.getHandler(handlerName) != null) {
                return registry;
            }
        }
        throw new UnsupportedOperationException(
                "No known regsitries exist for AbstractHandler by name : " + handlerName);
    }

    /**
     * Loads the proxy handler context from path specified in the HandlerConfigInfo. Looks for file by name ServiceProxyFrameworkConstants.SPRING_PROXY_HANDLER_CONFIG.
     * @param handlerConfigInfo containing absolute path to the proxy handler's configuration location i.e. folder
     */
    private void loadProxyHandlerContext(HandlerConfigInfo handlerConfigInfo) {
        // check if a context exists already for this config path
        for (HandlerConfigInfo loadedHandlerConfigInfo : this.handlerConfigInfoList) {
            if (loadedHandlerConfigInfo.equals(handlerConfigInfo)) {
                handlerConfigInfo = loadedHandlerConfigInfo;
                break;
            }
        }
        if (handlerConfigInfo.getProxyHandlerContext() != null) {
            // close the context and remove from list
            handlerConfigInfo.getProxyHandlerContext().close();
            this.handlerConfigInfoList.remove(handlerConfigInfo);
        }
        ClassLoader proxyHandlerCL = this.tccl;
        // check to see if the proxy has handler and dependent binaries deployed outside of the runtime class path. If yes, include them using a custom URL classloader.
        File customLibPath = new File(handlerConfigInfo.getXmlConfigFile().getParentFile(),
                HandlerConfigInfo.BINARIES_PATH);
        if (customLibPath.exists() && customLibPath.isDirectory()) {
            try {
                File[] libFiles = customLibPath.listFiles();
                URL[] libURLs = new URL[libFiles.length];
                for (int i = 0; i < libFiles.length; i++) {
                    libURLs[i] = new URL(HandlerConfigInfo.FILE_PREFIX + libFiles[i].getAbsolutePath());
                }
                proxyHandlerCL = new URLClassLoader(libURLs, this.tccl);
            } catch (MalformedURLException e) {
                throw new PlatformException(e);
            }
        }
        // now load the proxy handler context and add it into the handlerConfigInfoList list
        handlerConfigInfo.loadProxyHandlerContext(proxyHandlerCL,
                ServiceProxyComponentContainer.getCommonProxyHandlerBeansContext());
        this.handlerConfigInfoList.add(handlerConfigInfo);
    }

}