Package com.brienwheeler.apps.main

Source Code of com.brienwheeler.apps.main.ContextMain$PropertyLoadAction

/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Brien L. Wheeler (brienwheeler@yahoo.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.brienwheeler.apps.main;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

import com.brienwheeler.lib.jmx.MBeanRegistrationSupport;
import com.brienwheeler.lib.spring.beans.PropertyPlaceholderConfigurer;
import com.brienwheeler.lib.spring.beans.SmartClassPathXmlApplicationContext;

/**
* A class to process properties files and launch application contexts based upon command line
* arguments and a resource mapping file (by default located at classpath:resourceMap.xml).  Intended
* to be used as the MainClass of an executable JAR that includes a resource map, one or more
* application contexts, and all classes to support launch of those contexts.
*
* a "-m" command line argument can be used to redefine the location of the resource map.
* a "-n" command line argument can be used to not look for or process a resource map.
* a "-p" command line argument will result in processing one or more properties files
*     based on the next command line argument (which can be a file location or an alias
*     from the resource map)
* a "-c" command line argument will result in launching one or more application contexts
*     based on the next command line argument (which can be a beans file location or an alias
*     from the resource map).
*
* -p and -c options are processed in order.  That is, if the command line is
* "-c ctx1 -p props -c ctx2" then the props will only be in effect when ctx2 is launched.
*
* The -m and -n arguments are detected and handled before any properties files are processed
* or contexts are launched (since those arguments may depend on the resource map).
*
* See the Programmer's Guide for detailed information on the resourceMap.xml format.
*
* @author bwheeler
*/
@ManagedResource
public class ContextMain extends MainBase implements ApplicationListener<ContextClosedEvent>
{
  private static final Log log = LogFactory.getLog(ContextMain.class);
 
  private static final String RESOURCE_MAP = "classpath:resourceMap.xml";
  private static final String BEAN_DEFAULTCONTEXT = "defaultContext";
  private static final String BEAN_CONTEXTMAP = "contextMap";
  private static final String BEAN_PROPERTIESMAP = "propertiesMap";
  private static final String BEAN_SYSPROPS = "systemProperties";

  private static final String PROPSPEC_PREFIX = "properties[";
  private static final String PROPSPEC_SUFFIX = "]";

  private boolean shuttingDown = false;

  private final List<IAction> commandLineActions = new ArrayList<IAction>();

  private boolean useResourceMap = true;
  private Properties systemProperties = null;

  // context data
  private Properties contextMap = null; // loaded resouceMap contents
  private String defaultContext = null; // context to launch if no -c argument
  private String resourceMapLocation = RESOURCE_MAP;
  private boolean launchDefaultContext = true;
  private final Map<String,List<AbstractApplicationContext>> contextAliasMap =
      new HashMap<String,List<AbstractApplicationContext>>(); // processed definitions results
  private final List<AbstractApplicationContext> contextLaunchOrder =
      new ArrayList<AbstractApplicationContext>(); // order launched to reverse for shutdown
  private final Set<String> contextsResolving = new HashSet<String>(); // circular dependency prevention

  // properties data
  private Properties propertiesMap = null; // loaded resouceMap contents
  private final Set<String> propertiesProcessed = new HashSet<String>(); // processed properties tracking
  private final Set<String> propertiesResolving = new HashSet<String>(); // circular dependency prevention
 
  /**
   * Java main method to construct a ContextMain and run it.
   * @param args JVM command line arguments
   */
  public static void main(String[] args)
  {
    new ContextMain(args).run();
  }
 
  /**
   * Construct the ContextMain object.
   * @param args command line arguments specifying the properties files to process and the
   * application contexts to launch.
   */
  public ContextMain(String[] args)
  {
    super(args);
    // we will process our own command line so that property load operations are sequenced
    // with context load operations, not all done before context loads
    super.setUseBaseOpts(false);
  }

  @Override
  protected boolean processCommandLineOption(CommandLine commandLine)
  {
    // here for good form and just in case useBaseOpts is turned to true in the future
    if (super.processCommandLineOption(commandLine))
      return true;

    String opt = commandLine.peekNextArg();
   
    if (opt.equals("-p"))
    {
      commandLineActions.add(new PropertyLoadAction(commandLine.skipAndGetNextArg("-p option must have a value")));
      return true;
    }

    if (opt.equals("-c"))
    {
      commandLineActions.add(new ContextLoadAction(commandLine.skipAndGetNextArg("-c option must have a value")));
      launchDefaultContext = false;
      return true;
    }

    if (opt.equals("-m"))
    {
      resourceMapLocation = commandLine.skipAndGetNextArg("-m option must have a value");
      return true;
    }

    if (opt.equals("-n"))
    {
      commandLine.getNextArg();
      useResourceMap = false;
      return true;
    }

    return false;
  }

  @Override
  protected void onRun()
  {
    MBeanRegistrationSupport.registerMBean(this);
   
    if (useResourceMap)
      loadResourceMap();
   
    // before launching any contexts, process any properties specified in the resourceMap
    if (systemProperties != null)
      PropertyPlaceholderConfigurer.processProperties(systemProperties);
   
    for (IAction action : commandLineActions)
      action.execute();
    if (launchDefaultContext && defaultContext != null)
      findOrLaunchContext(defaultContext);
   
    waitForAllContexts();
  }

  private <T> T getResource(ApplicationContext context, String resourceName, Class<T> clazz)
  {
    try {
      return context.getBean(resourceName, clazz);
    }
    catch (NoSuchBeanDefinitionException e) {
      // ok
    }
    catch (Exception e) {
      log.error("error loading resourceMap element " + resourceName, e);
    }
    return null;
  }
 
  private void loadResourceMap()
  {
    ApplicationContext context = new ClassPathXmlApplicationContext(resourceMapLocation);
    defaultContext = getResource(context, BEAN_DEFAULTCONTEXT, String.class);
    systemProperties = getResource(context, BEAN_SYSPROPS, Properties.class);
    contextMap = getResource(context, BEAN_CONTEXTMAP, Properties.class);
    propertiesMap = getResource(context, BEAN_PROPERTIESMAP, Properties.class);
  }
 
  private void loadPropertySpec(String propertySpecList)
  {
    if (propertiesProcessed.contains(propertySpecList))
      return;
   
    if (propertiesResolving.contains(propertySpecList))
      throw new ResourceMapError("circular dependency: " + propertiesResolving);
    propertiesResolving.add(propertySpecList);
   
    try {
      // if the list has more than one spec string, split then iteratively recurse
      if (propertySpecList.contains(",")) {
        String[] propertySpecs = propertySpecList.split(",");
        for (String propertySpec : propertySpecs)
          loadPropertySpec(propertySpec);
        return;
      }
     
      // ok, propertySpecList has just one spec string in it.  Possibly alias or location.
     
      // check context map and recurse if present
      if (propertiesMap != null) {
        String resolvedSpecList = propertiesMap.getProperty(propertySpecList);
        if (resolvedSpecList != null) {
          loadPropertySpec(resolvedSpecList);
          return;
        }
      }

      // not an alias, must be a location at this point
      PropertyPlaceholderConfigurer.processLocation(propertySpecList);
      propertiesProcessed.add(propertySpecList);
    }
    finally {
      propertiesResolving.remove(propertySpecList);
    }
  }
 
  private List<AbstractApplicationContext> findOrLaunchContext(String contextSpecList)
  {
    // check to see if we've already figured this out
    List<AbstractApplicationContext> resultingContexts = contextAliasMap.get(contextSpecList);
    if (resultingContexts != null)
      return resultingContexts;

    // circular dependency detection
    if (contextsResolving.contains(contextSpecList))
      throw new ResourceMapError("circular dependency: " + contextSpecList);
    String originalContextSpecList = contextSpecList;
    contextsResolving.add(originalContextSpecList);
   
    try {
      // create container for eventual results
      resultingContexts = new ArrayList<AbstractApplicationContext>();
     
      // first check to see if it has a properties prefix and process/strip it from spec string
      // do this before looking for comma separated list below so that a comma-separated list
      // within the properties prefix gets handled correctly
      if (contextSpecList.startsWith(PROPSPEC_PREFIX)) {
        int specEnd = contextSpecList.indexOf(PROPSPEC_SUFFIX);
        if (specEnd == -1)
          throw new ResourceMapError("unterminated property spec in " + contextSpecList);
       
        String propertySpecList = contextSpecList.substring(PROPSPEC_PREFIX.length(), specEnd);
        contextSpecList = contextSpecList.substring(specEnd + PROPSPEC_SUFFIX.length()).trim();
        loadPropertySpec(propertySpecList);
      }

      // if the list has more than one spec string, split then recurse for first spec and remaining spec list
      int comma = contextSpecList.indexOf(",");
      if (comma != -1) {
        String currentSpec = contextSpecList.substring(0, comma).trim();
        resultingContexts.addAll(findOrLaunchContext(currentSpec));
        String remainingSpecList = contextSpecList.substring(comma + 1).trim();
        resultingContexts.addAll(findOrLaunchContext(remainingSpecList));
        contextAliasMap.put(contextSpecList, resultingContexts);
        return resultingContexts;
      }
 
      // be robust against a properties spec with no context -- user might do something like this:
      // properties[props1],context1,properties[props2],context2

      // in any case, an empty contextSpecList implies no further action
      if (contextSpecList.isEmpty()) {
        return resultingContexts;
      }
     
      // ok, contextSpecList has just one spec string in it.  Possibly alias or location.
 
      // check context map and recurse if present
      if (contextMap != null) {
        String resolvedSpecList = contextMap.getProperty(contextSpecList);
        if (resolvedSpecList != null) {
          resultingContexts.addAll(findOrLaunchContext(resolvedSpecList));
          contextAliasMap.put(contextSpecList, resultingContexts);
          return resultingContexts;
        }
      }
     
      // not an alias, must be a location at this point
      AbstractApplicationContext context = new SmartClassPathXmlApplicationContext(contextSpecList);
      synchronized (contextLaunchOrder) {
        // this allows a context to close itself during its startup (schematool does this)
        if (context.isActive())
          contextLaunchOrder.add(context);
        context.addApplicationListener(this);
      }
      resultingContexts.add(context);
      contextAliasMap.put(contextSpecList, resultingContexts);
      return resultingContexts;
    }
    finally {
      contextsResolving.remove(originalContextSpecList);
    }
  }
 
  private void waitForAllContexts()
  {
    try {
      synchronized (contextLaunchOrder) {
        onWaitingForShutdown();
        while (contextLaunchOrder.size() > 0)
          contextLaunchOrder.wait();
      }
    }
    catch (InterruptedException e) {
      log.error("interrupted waiting for context shutdown");
      Thread.currentThread().interrupt();
      return;
    }
  }
 
  @ManagedOperation
  public String shutdown()
  {
    AbstractApplicationContext context = null;
    int n = 0;

    do {
      context = null;
      synchronized (contextLaunchOrder) {
        shuttingDown = true;
        if (contextLaunchOrder.size() > 0)
          context = contextLaunchOrder.remove(contextLaunchOrder.size() - 1);
      }
     
      if (context != null) {
        context.close();
        n++;
      }
    } while (context != null);
   
    synchronized (contextLaunchOrder) {
      shuttingDown = false;
      contextLaunchOrder.notifyAll();
    }
    return "shutdown " + n + " contexts";
  }

  @Override
  public void onApplicationEvent(ContextClosedEvent event)
  {
    final AbstractApplicationContext context = (AbstractApplicationContext) event.getApplicationContext();
    boolean waitAndSignal = false;
   
    synchronized (contextLaunchOrder) {
      contextLaunchOrder.remove(context);
      // if that was the last context and it self-terminated
      if (contextLaunchOrder.size() == 0 && !shuttingDown)
        waitAndSignal = true;
    }
   
    if (waitAndSignal) {
      // since the ContextClosedEvent actually comes at the start of the close process,
      // start a background thread to monitor the state of the context and signal
      // the thread waiting in onRun() when it is finished closing.
      new Thread() {
        @Override
        public void run()
        {
          while (context.isActive()) {
            try {
              Thread.sleep(100L);
            }
            catch (InterruptedException e) {
              log.error("interrupted while waiting for last context to finish shutdown");
              Thread.currentThread().interrupt();
            }
          }
         
          synchronized (contextLaunchOrder) {
            contextLaunchOrder.notifyAll();
          }
        }
      }.start();
    }
  }
 
  protected void onWaitingForShutdown() {} // testability
 
  /**
   * Polymorphic interface to represent command line actions that are executed in order
   * as present on the command line.
   *
   * @author bwheeler
   */
  private static interface IAction
  {
    void execute();
  }
 
  /**
   * Command line action to load properties
   *
   * @author bwheeler
   */
  private class PropertyLoadAction implements IAction
  {
    private String propertyLocation;

    public PropertyLoadAction(String propertyLocation)
    {
      this.propertyLocation = propertyLocation;
    }

    @Override
    public void execute()
    {
      loadPropertySpec(propertyLocation);
    }

    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + propertyLocation + "]";
    }
  }

  /**
   * Command line action to load contexts
   *
   * @author bwheeler
   */
  private class ContextLoadAction implements IAction
  {
    private String contextLocation;

    public ContextLoadAction(String contextLocation)
    {
      this.contextLocation = contextLocation;
    }

    @Override
    public void execute()
    {
      findOrLaunchContext(contextLocation);
    }
   
    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + contextLocation + "]";
    }
  }
}
TOP

Related Classes of com.brienwheeler.apps.main.ContextMain$PropertyLoadAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.