/** * 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; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Map.Entry; import javax.xml.namespace.QName; import org.apache.commons.jxpath.JXPathContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xchain.Catalog; import org.xchain.Command; import org.xchain.framework.factory.CatalogFactory; import org.xchain.framework.jxpath.ScopedQNameVariables; import org.xchain.framework.lifecycle.Execution; import org.xchain.framework.lifecycle.ExecutionException; import org.xchain.framework.lifecycle.ExecutionTraceElement; import org.xchain.framework.lifecycle.Lifecycle; import org.xchain.framework.lifecycle.LifecycleException; import org.xml.sax.Locator; /** * <p>Bootstraps the environment for executing an XChain. It used an XML configuration file for * determining what catalog to use and which commands to execute.</p> * * @author Josh Kennedy */ public class StandAloneExecutor implements Executor { public static final String CONFIGURATION_SYSTEM_PROPERTY = "org.xchain.executor.configuration"; public static final String DATASETLOADER_CONFIGURATION_DEFAULT = "executor.xml"; public static final String CATALOG_URI = "{http://www.xchain.org/}catalog"; public static final String EXECUTE_COMMAND = "{http://www.xchain.org/}command"; public static final Logger log = LoggerFactory.getLogger(StandAloneExecutor.class); private Properties properties; /** * Complete any configuration needed for starting the Life Cycle */ public void initLifeCycle() { } /** * Start the Life Cycle */ public void startLifeCycle() throws LifecycleException { Lifecycle.startLifecycle(); } /** * Get the given Catalog based on the property CATALOG_URI */ public Catalog getCatalog(String uri) throws CatalogNotFoundException, CatalogLoadException { return CatalogFactory.getInstance().getCatalog(uri); } /** * Get the command off of the Catalog */ public Command getCommand(Catalog catalog, String cmd) throws CommandNotFoundException { return catalog.getCommand(cmd); } /** * Create the JXPathContext that is going to be used */ public JXPathContext getContext() { return JXPathContext.newContext(new HashMap<Object, Object>()); } /** * Calls configure context passing in the properties configuration */ public void configureContext(JXPathContext context, Map<QName, Object> variables) { ScopedQNameVariables contextVariables = (ScopedQNameVariables) context.getVariables(); for (Entry<QName, Object> entry : variables.entrySet()) { contextVariables.declareVariable(entry.getKey(), entry.getValue()); } } /** * Load the properties configuration into the Context, while maintaining proper scoping * if it's present in the variable name * * @param context * @param properties */ protected void configureContext(JXPathContext context, Properties properties) { HashMap<QName, Object> variables = new HashMap<QName, Object>(); for (Object key : properties.keySet()) { variables.put(new QName((String) key), properties.get(key)); } configureContext(context, variables); } /** * Stop the Life Cycle */ public void stopLifeCycle() throws LifecycleException { Lifecycle.stopLifecycle(); } /** * Step through the process of using an XChain in the needed order * * @throws Exception */ public void execute() throws Exception { // Step 1: Configure Lifecycle initLifeCycle(); // Step 2: Start life cycle startLifeCycle(); try { // Step 3: Get the catalog Catalog catalog = getCatalog(properties.getProperty(CATALOG_URI)); // Step 4: Get the command Command command = getCommand(catalog, properties.getProperty(EXECUTE_COMMAND)); // Step 5: Get the context JXPathContext context = getContext(); // Step 5.5: Configure Context configureContext(context, properties); // Step 6: Execute the command command.execute(context); } catch (ExecutionException e) { printStack(e.getMessage(), "error", e.getExecutionTrace()); throw e; } catch (Exception e) { printStack(e.getMessage(), "error"); throw e; } finally { // Step 8: Stop life cycle Lifecycle.stopLifecycle(); } } /** * Retrieve the properties configuration that the Executor is using * @return */ public Properties getProperties() { return properties; } /** * Set the properties configuration that the Executor will use * @param properties */ public void setProperties(Properties properties) { this.properties = properties; } public void printStack(String message) { printStack(message, "warn"); } public void printStack(String message, String level) { printStack(message, level, Execution.getExecutionTrace()); } public void printStack(String message, String level, List<ExecutionTraceElement> stack) { StringBuffer buffer = new StringBuffer(); buffer.append(message); buffer.append("\n"); for (ExecutionTraceElement element : stack) { Locator locator = element.getLocator(); buffer.append("\t running "); buffer.append(element.getQName().toString()); buffer.append(" in "); buffer.append(element.getSystemId()); buffer.append(" at "); buffer.append(locator.getLineNumber()); buffer.append(":"); buffer.append(locator.getColumnNumber()); buffer.append("\n"); } if (level.toLowerCase().equals("trace")) { log.trace(buffer.toString()); } if (level.toLowerCase().equals("debug")) { log.debug(buffer.toString()); } if (level.toLowerCase().equals("info")) { log.info(buffer.toString()); } if (level.toLowerCase().equals("warn")) { log.warn(buffer.toString()); } if (level.toLowerCase().equals("error")) { log.error(buffer.toString()); } } /** * This method is used to populate the defaults for the properties that will be used to execute * the XChain. * * @return */ protected static Properties getDefaultProperties() { return new Properties(){ private static final long serialVersionUID = -3730355766429570858L; { this.setProperty(EXECUTE_COMMAND, "dispatcher"); this.setProperty("command", "latest"); } }; } /** * Attempts to load properties based off of the three methods to define where a * configuration file is. First it will try the "standard" configuration placement * "./executor.xml", then it will look for a jvm property CONFIGURATION_SYSTEM_PROPERTY, * and finally it will check to see if a file name was passed in * * @param properties Properties Object to use * @param args Arguments passed in from the command line * @return */ protected static boolean loadProperties(Properties properties, String[] args) { boolean defaultProp = false; boolean jvmProp = false; boolean cmdProp = false; // This follows the order of default, JVM Param, and finally command line // As they are loaded, each new one will overwrite any properties that were // loaded prior, but leave any that don't exist in the current file defaultProp = loadPropertiesFromFile(DATASETLOADER_CONFIGURATION_DEFAULT, properties); if (System.getProperty(CONFIGURATION_SYSTEM_PROPERTY) != null) { jvmProp = loadPropertiesFromFile(System.getProperty(CONFIGURATION_SYSTEM_PROPERTY), properties); } if (args.length > 0) { cmdProp = loadPropertiesFromFile(args[0], properties); } if (defaultProp || jvmProp || cmdProp) { return true; } else { return false; } } /** * Attempts to load the properties from both the regular file system, as well as * from the contents of the class loader. If there are files in both locations * they will both be loaded into the properties, the file system takes precedence. * * @param name file name * @param properties properties object to load file into * @return */ protected static boolean loadPropertiesFromFile(String name, Properties properties) { boolean status = false; InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name); File propertiesFile = new File(name); if (stream != null) { try { properties.loadFromXML(stream); status = true; } catch (IOException e) { log.debug("Unable to read configuration file '" + name + "'", e); } finally { try { stream.close(); } catch (NullPointerException e) { } catch (IOException e) { } } } if (propertiesFile != null && propertiesFile.exists()) { try { stream = new FileInputStream(propertiesFile); properties.loadFromXML(stream); status = true; } catch (FileNotFoundException e) { log.debug("Unable to find configuration file '" + name + "'", e); } catch (IOException e) { log.debug("Unable to load configuration file '" + name + "'", e); } finally { try { stream.close(); } catch (NullPointerException e) { } catch (IOException e) { } } } return status; } public static void main(String[] args) { // Set some sane defaults . . . Properties properties = getDefaultProperties(); if (!loadProperties(properties, args)) { log.warn("Unable to find and load a configuration, proceeding with defaults. Please see the usage documentaion."); } try { StandAloneExecutor executor = new StandAloneExecutor(); executor.setProperties(properties); executor.execute(); } catch (Exception e) { log.error("There was an unexpected exception while executing the xchain", e); } } }