org.metis.cassandra.CqlComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.metis.cassandra.CqlComponent.java

Source

/**
 * 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.metis.cassandra;

import java.io.File;
import java.util.Map;

import static org.metis.utils.Constants.*;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.impl.DefaultComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Represents the component that manages {@link CqlEndpoint}.
 */
public class CqlComponent extends DefaultComponent {

    private static final Logger LOG = LoggerFactory.getLogger(CqlComponent.class);

    // This is the system property used for overriding the default name of the
    // Spring application context
    private static final String CASSY_CONTEXT_PROP = "metis.cassandra.spring.context";

    // This system property is used for enabling the background thread that
    // monitors the Spring application context file.
    private static final String CASSY_CONTEXT_MON = "metis.cassandra.context.monitor";

    // the default setting for starting the context monitor
    private static boolean dfltStartMonitor = false;

    // the 'start monitor' setting for this instance of the CqlComponent
    private Boolean startMonitor;

    // The default name of the Spring application context file.
    public static final String CASSY_DFLT_CONTEXT = "cassandra.xml";

    // used for tucking the endpoint into an exchange
    protected static final String CASSY_ENDPOINT_PROP = "org.metis.cassandra.endpoint";

    // The default CASSY context file name, which can be overridden
    // via a system property
    private static String dfltContextFileName = null;

    // The CASSY context file name used by this instance of the
    // component
    private String myContextFileName = null;

    private ComponentProfile componentProfile = null;

    private Map<String, Client> clients;

    static {
        // check to see if the default context name has been
        // overridden
        dfltContextFileName = System.getProperty(CASSY_CONTEXT_PROP);
        // if not, then use the default name
        if (dfltContextFileName == null) {
            dfltContextFileName = CASSY_DFLT_CONTEXT;
        }
        // see if the monitor is supposed to start
        if (System.getProperty(CASSY_CONTEXT_MON) != null) {
            dfltStartMonitor = true;
        }
    }

    /**
     * This constructor is implicitly called by Camel core. For example:
     * <p>
     * <blockquote>
     * 
     * <pre>
     * CamelContext context = new DefaultCamelContext();
     * context.addRoutes(new RouteBuilder() {
     *   public void configure() {
     *     from("test-jms:queue:test.queue").to("cassandra://uri");
     *   }
     * });
     * </blockquote>
     * </pre>
     */
    public CqlComponent() {
    }

    public CqlComponent(CamelContext context) {
        super(context);
    }

    /**
     * Static builder method. The method allows the end-user to explicitly add a
     * CqlComponent to a camel context. For example,
     * <p>
     * <blockquote>
     * 
     * <pre>
     * CamelContext context = new DefaultCamelContext();
     * context.addComponent("cql",CqlComponent.cqlComponent());
     * </blockquote>
     * </pre>
     * 
     * @return CqlComponent
     */
    public static CqlComponent cqlComponent() {
        return new CqlComponent();
    }

    /**
     * Static builder method. The method allows the end-user to explicitly add a
     * CqlComponent to a camel context. For example,
     * <p>
     * <blockquote>
     * 
     * <pre>
     * CamelContext context = new DefaultCamelContext();
     * context.addComponent("cql",CqlComponent.cqlComponent("cassandra.xml"));
     * </blockquote>
     * </pre>
     * 
     * @param contextFileName
     *            the name of this component's Spring XML context file, which
     *            must be in the classpath.
     * @return CqlComponent
     */
    public static CqlComponent cqlComponent(String contextFileName) {
        CqlComponent cc = new CqlComponent();
        cc.setContextFileName(contextFileName);
        return cc;
    }

    /**
     * Set the name of the Spring XML context file for this component. The
     * context file, which must be in the classpath, holds the Client beans and
     * must include Cassandra ClusterBeans.
     * 
     * This method is called by a static builder method or whenever this
     * component is configured via a Spring XML file.
     * 
     * <p>
     * <blockquote>
     * 
     * <pre>
     * <bean id="cassandra" class="org.metis.cassandra.CqlComponent"> <property
     * name="contextFileName" value="cassandra.xml"/> </bean>
     * 
     * </blockquote>
     * </pre>
     * 
     * An end-user can stop this component, update this property, and then
     * re-start it. When re-started, the component will re-initialize itself
     * with this new context file.
     * 
     * @param contextFileName
     *            the name of this component's Spring XML file, which must be in
     *            the classpath
     */
    public void setContextFileName(String contextFileName) {
        myContextFileName = contextFileName;
    }

    /**
     * Return the Spring context file name being used by this instance of the
     * CqlComponent
     * 
     * @return String file name
     */
    public String getContextFileName() {
        return myContextFileName;
    }

    /**
     * Return a file handle to the Spring context file currently being used by
     * this instance of the CqlComponent. Note that this method returns null if
     * the Component has never been started.
     * 
     * @return String file name
     */
    public File getContextFile() {
        return (getComponentProfile() == null ? null : getComponentProfile().getContextFile());
    }

    /**
     * Used for specifying whether to monitor this component's Spring context
     * file. The context file, which must be in the classpath, holds the RDB
     * mapper bean, RDBs and Cassandra Java Driver.
     * 
     * This method is called whenever this component is configured via a Spring
     * XML file. Within the Spring XML file, the configuration property is set
     * to 'true' or 'false'. Actually, anything other than 'true' will result in
     * 'false'.
     * 
     * This method must be called prior to starting the component.
     * 
     * <p>
     * <blockquote>
     * 
     * <pre>
     * <bean id="cassandra" class="org.metis.cassandra.CqlComponent"> <property
     * name="startMonitor" value="true"/> </bean>
     * 
     * </blockquote>
     * </pre>
     * 
     * @param setting
     *            true or false
     */
    public void setStartMonitor(String setting) {
        try {
            startMonitor = Boolean.valueOf(setting);
        } catch (Exception e) {
            LOG.error("setStartMonitor: invalid string = " + setting);
        }
    }

    /**
     * Return the startMonitor Boolean, which indicates whether the component
     * was instructed to monitor its Spring context file.
     * 
     * @return Boolean
     */
    public Boolean getStartMonitor() {
        if (startMonitor == null) {
            startMonitor = new Boolean(dfltStartMonitor);
        }
        return startMonitor;
    }

    /**
     * Return a boolean, which indicates whether the component was instructed to
     * monitor its Spring context file.
     * 
     * @return boolean
     */
    public boolean isStartMonitor() {
        return getStartMonitor().booleanValue();
    }

    /**
     * Return the Profile object currently being used by this instance of the
     * CqlComponent. Note that this method returns null if the Component has
     * never been started.
     */
    public ComponentProfile getComponentProfile() {
        return componentProfile;
    }

    protected void setComponentProfile(ComponentProfile componentProfile) {
        this.componentProfile = componentProfile;
    }

    /**
     * This method is called by camel core to get a CqlEndpoint. Assumption is
     * made that this method must be multi-thread capable and must also be aware
     * of this component's lifecycle.
     */
    public Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {

        if (LOG.isTraceEnabled()) {
            LOG.trace("createEndpoint: Component entered");
            LOG.trace("createEndpoint: uri = " + uri);
            LOG.trace("createEndpoint: remaining = " + remaining);
        }

        // do nothing if this component has been shutdown or stopped
        // if you stop the component, then it must be explicitly started!
        if (isStopped()) {
            LOG.warn("createEndpoint: component has been stopped");
            // throw new
            // Exception("createEndpoint: component has been stopped");
        }

        // do nothing if this component has been suspended
        // if it has been suspended, then you must explicitly resume it!
        if (isSuspended()) {
            LOG.warn("createEndpoint: component has been suspended");
            // throw new
            // Exception("createEndpoint: component has been suspended");
        }

        // wait while component is in the process of starting/initializing
        while (isStarting()) {
            LOG.warn("createEndpoint: waiting for component to start");
            try {
                this.wait(500);
            } catch (InterruptedException ignore) {
            }
        }

        // if not started, then attempt to start, which includes initializing
        // the component. Note that if previously stopped, then this would be a
        // re-start and re-initialization
        if (!isStarted()) {
            LOG.trace("createEndpoint: starting component");
            // invoke our super class's start() method, which calls this
            // component's doStart()
            try {
                start();
            } finally {
                // wake up any threads that may have been waiting for this
                // component to be started
                notifyAll();
            }
        }

        // So far so good, begin to service the route's request. Make sure we
        // have a proper URI (context-path)
        if (remaining == null || remaining.isEmpty()) {
            throw new Exception("createEndpoint: null or empty URI context-path provided");
        }

        // the context path must start with a '/' in order to give to the
        // ClientMapper
        String contextPath = remaining;
        if (!contextPath.startsWith(FORWARD_SLASH_STR)) {
            contextPath = FORWARD_SLASH_STR + contextPath;
        }

        // if there is a ClientMapper, let it map the request to a Client
        // else, just try and do a straight map
        Object obj = (getComponentProfile().getClientMapper() != null)
                ? getComponentProfile().getClientMapper().lookupHandler(contextPath)
                : getClients().get(remaining);

        if (obj == null) {
            throw new Exception("createEndpoint: Client not found for this context path: " + remaining);
        }

        if (!(obj instanceof Client)) {
            throw new Exception("createEndpoint: returned object from context returns bean of type "
                    + obj.getClass().getName() + " instead of " + Client.class.getName());
        }

        LOG.trace("createEndpoint: Got this Client {} ", ((Client) obj).getBeanName());

        // create and return a CqlEndpoint
        return new CqlEndpoint(uri, contextPath, this, parameters, (Client) obj);

    }

    // Prevent introspection of URI parameters on the created Endpoint instance
    @Override
    protected boolean useIntrospectionOnEndpoint() {
        return false;
    }

    // starts/initializes the component. Camel core should call the super's
    // start() method, which then calls this method. Note that this component
    // extends ServiceSupport where all the higher level lifecycle methods are
    // found
    @Override
    protected void doStart() throws Exception {

        LOG.trace("doStart: enter");

        // a component that has been shut down cannot be restarted; only stopped
        // components can be restarted
        if (isShutdown()) {
            throw new Exception("component has been shut down");
        }
        // okay to proceed, begin initialization
        initComponent();
    }

    @Override
    // stops the component - goes back to being uninitialized
    // the super's stop() method will call this doStop, but only
    // if the component had not already been stopped
    protected void doStop() throws Exception {

        LOG.trace("doStop: enter");

        if (isShutdown()) {
            LOG.warn("component has already been shut down");
            return;
        } else if (!isSuspended() && !isStarted()) {
            LOG.trace("doStop: component had never been started");
            return;
        }

        // component is either suspended or started

        if (getComponentProfile() != null) {
            // stop the context
            if (getComponentProfile().getCassandraContext() != null) {
                getComponentProfile().getCassandraContext().stop();
            }
            // stop the profile monitor
            getComponentProfile().stop();
        }
        setComponentProfile(null);
    }

    // ---------- Begin Initialization Methods -----------------------

    // begins the initialization process. the first thing is to get the Spring
    // XML file for this component.
    private void initComponent() throws Exception {

        if (LOG.isTraceEnabled()) {
            LOG.trace("initComponent: Component has not yet been initialized");
            LOG.trace("initComponent: Using this context file name: " + dfltContextFileName);
        }

        ConfigurableApplicationContext cassandraContext = null;

        // load the spring application context, which should be somewhere
        // in the application's classpath
        if (myContextFileName == null) {
            myContextFileName = dfltContextFileName;
        }
        try {
            cassandraContext = new ClassPathXmlApplicationContext(myContextFileName);
            setClients(cassandraContext.getBeansOfType(Client.class));
        } catch (BeansException exc) {
            LOG.error("initComponent: caught this exception while " + "attempting to load spring context file: "
                    + exc.toString());
            throw exc;
        }

        if (getClients() == null || getClients().isEmpty()) {
            cassandraContext.close();
            throw new Exception("there are no beans of type Client in the Spring XML file");
        }

        // now that the context has been successfully loaded, create a new
        // profile and continue the init process
        setComponentProfile(new ComponentProfile(cassandraContext, myContextFileName, this));

        initClientMapper(cassandraContext);
        initProfileMonitor();

    }

    /**
     * Initialize the ClientMappers used by this component. Note that this
     * initial implementation only supports one mapper.
     * <p>
     */
    private void initClientMapper(ConfigurableApplicationContext cassandraContext) throws Exception {

        ClientMapper clientMapper = null;
        try {
            // find the one client mapper
            clientMapper = cassandraContext.getBean(ClientMapper.class);
        } catch (NoSuchBeanDefinitionException ignore) {
        }

        if (clientMapper != null) {
            getComponentProfile().setClientMapper(clientMapper);
            LOG.trace("initClientMapper: using a client mapper");
        } else {
            LOG.trace("initClientMapper: not using a client mapper");
        }
    }

    // checks to see if profile monitor thread is to be activated
    private void initProfileMonitor() {
        if (isStartMonitor()) {
            try {
                getComponentProfile().start();
            } catch (Exception e) {
                LOG.error(
                        "initProfileMonitor: unable to start profile because of this exception: " + e.getMessage());
            }
        } else {
            if (LOG.isTraceEnabled()) {
                LOG.trace("initProfileMonitor: profile not started");
            }
        }
    }

    private boolean isShutdown() {
        return shutdown.get();
    }

    /**
     * @return the clients
     */
    public Map<String, Client> getClients() {
        return clients;
    }

    /**
     * @param clients
     *            the clients to set
     */
    public void setClients(Map<String, Client> clients) {
        this.clients = clients;
    }

    // ---------- End Initialization Methods -----------------------

}