]> git.argeo.org Git - gpl/argeo-slc.git/blobdiff - runtime/org.argeo.slc.launcher/src/main/java/org/argeo/slc/cli/SlcMain.java
Clean OSGi runtime on start
[gpl/argeo-slc.git] / runtime / org.argeo.slc.launcher / src / main / java / org / argeo / slc / cli / SlcMain.java
index 04caaab9eef388d907b755c4ba3779a8aafa6eba..0d92b6a1f210f6efdfb3bb37e22bf8b794015f17 100644 (file)
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * 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.argeo.slc.cli;
 
-import java.io.FileInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.Properties;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.GnuParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.OptionBuilder;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.slc.SlcException;
-import org.argeo.slc.logging.Log4jUtils;
-import org.argeo.slc.runtime.SlcExecutionContext;
-import org.argeo.slc.runtime.SlcRuntime;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-import org.springframework.context.support.FileSystemXmlApplicationContext;
-
-public class SlcMain {
-       public enum Mode {
-               single, agent
-       }
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.osgi.boot.OsgiBoot;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+/** Configures an SLC runtime and runs a process. */
+public class SlcMain implements PrivilegedAction<String> {
+       public final static String NIX = "NIX";
+       public final static String WINDOWS = "WINDOWS";
+       public final static String SOLARIS = "SOLARIS";
+
+       public final static String os;
+       public final static String slcDirName = ".slc";
+       final static File homeDir = new File(System.getProperty("user.home"));
 
-       private static Log log = null;
+       static {
+               String osName = System.getProperty("os.name");
+               if (osName.startsWith("Win"))
+                       os = WINDOWS;
+               else if (osName.startsWith("Solaris"))
+                       os = SOLARIS;
+               else
+                       os = NIX;
+       }
 
-       private final static String BOOTSTRAP_LOG4J_CONFIG = "org/argeo/slc/cli/bootstrapLog4j.properties";
-       private final static String DEFAULT_AGENT_CONTEXT = "classpath:org/argeo/slc/cli/spring-agent-default.xml";
+       private Long timeout = 30 * 1000l;
+       private final String[] args;
+       private final File confDir;
+       private final File dataDir;
+       private final File modulesDir;
+
+       private final List<String> bundlesToStart = new ArrayList<String>();
+
+       public SlcMain(String[] args, File confDir, File dataDir, File modulesDir) {
+               this.args = args;
+               this.confDir = confDir;
+               this.dataDir = dataDir;
+               this.modulesDir = modulesDir;
+               bundlesToStart.add("org.springframework.osgi.extender");
+               bundlesToStart.add("org.argeo.node.repo.jackrabbit");
+               bundlesToStart.add("org.argeo.security.dao.os");
+               bundlesToStart.add("org.argeo.slc.node.jackrabbit");
+               bundlesToStart.add("org.argeo.slc.agent");
+               bundlesToStart.add("org.argeo.slc.agent.jcr");
+               if (args.length == 0)
+                       bundlesToStart.add("org.argeo.slc.support.equinox");
+               // bundlesToStart.add("org.argeo.slc.agent.cli");
+       }
 
-       private final static Option modeOpt = OptionBuilder.withLongOpt("mode")
-                       .withArgName("mode").hasArg().withDescription(
-                                       "SLC execution mode, one of: " + listModeValues()).create(
-                                       'm');
+       public String run() {
+               long begin = System.currentTimeMillis();
 
-       private final static Option propertyOpt = OptionBuilder.withLongOpt(
-                       "property").withArgName("prop1=val1,prop2=val2").hasArgs()
-                       .withValueSeparator(',').withDescription(
-                                       "use value for given property").create('p');
+               Framework framework = null;
+               try {
+                       info("## Date : " + new Date());
+                       info("## Data : " + dataDir.getCanonicalPath());
+
+                       // Start Equinox
+                       ServiceLoader<FrameworkFactory> ff = ServiceLoader
+                                       .load(FrameworkFactory.class);
+                       FrameworkFactory frameworkFactory = ff.iterator().next();
+                       Map<String, String> configuration = new HashMap<String, String>();
+                       configuration.put("osgi.configuration.area",
+                                       confDir.getCanonicalPath());
+                       configuration.put("osgi.instance.area", dataDir.getCanonicalPath());
+                       // Do clean
+                       configuration.put("osgi.clean", "true");
+                       if (args.length == 0) {
+                               configuration.put("osgi.console", "");
+                       }
 
-       private final static Option propertiesOpt = OptionBuilder.withLongOpt(
-                       "properties").withArgName("properties file").hasArgs()
-                       .withValueSeparator(',').withDescription(
-                                       "load properties from file (-p has priority)").create('P');
+                       // Spring configs currently require System properties
+                       System.getProperties().putAll(configuration);
+
+                       framework = frameworkFactory.newFramework(configuration);
+                       framework.start();
+                       BundleContext bundleContext = framework.getBundleContext();
+
+                       // OSGi bootstrap
+                       OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
+
+                       // working copy modules
+                       if (modulesDir.exists())
+                               osgiBoot.installUrls(osgiBoot.getBundlesUrls(modulesDir
+                                               .getCanonicalPath() + ";in=*;ex=.gitignore"));
+
+                       // system modules
+                       if (System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BUNDLES) != null)
+                               osgiBoot.installUrls(osgiBoot.getBundlesUrls(System
+                                               .getProperty(OsgiBoot.PROP_ARGEO_OSGI_BUNDLES)));
+                       else
+                               osgiBoot.installUrls(osgiBoot.getBundlesUrls(System
+                                               .getProperty("user.home") + "/.slc/modules/;in=**"));
+
+                       // Start runtime
+                       osgiBoot.startBundles(bundlesToStart);
+
+                       // Find SLC Agent
+                       ServiceReference sr = null;
+                       while (sr == null) {
+                               sr = bundleContext
+                                               .getServiceReference("org.argeo.slc.execution.SlcAgentCli");
+                               if (System.currentTimeMillis() - begin > timeout)
+                                       throw new RuntimeException("Cannot find SLC agent CLI");
+                               Thread.sleep(100);
+                       }
+                       Object agentCli = bundleContext.getService(sr);
 
-       private final static Option scriptOpt = OptionBuilder.withLongOpt("script")
-                       .withArgName("script").hasArg().withDescription(
-                                       "SLC script to execute").create('s');
+                       // Initialization completed
+                       long duration = System.currentTimeMillis() - begin;
+                       info("[[ Initialized in " + (duration / 1000) + "s "
+                                       + (duration % 1000) + "ms ]]");
 
-       private final static Option targetsOpt = OptionBuilder.withLongOpt(
-                       "targets").withArgName("targets").hasArg().withDescription(
-                       "Targets to execute").create('t');
+                       if (args.length == 0)
+                               return null;// console mode
 
-       private final static Option runtimeOpt = OptionBuilder.withLongOpt(
-                       "runtime").withArgName("runtime").hasArg().withDescription(
-                       "Runtime to use, either a full path or relative to slc app conf dir: "
-                                       + "<conf dir>/runtime/<runtime>/.xml").create('r');
+                       // Subject.doAs(Subject.getSubject(AccessController.getContext()),
+                       // new AgentCliCall(agentCli));
+                       Class<?>[] parameterTypes = { String[].class };
+                       Method method = agentCli.getClass().getMethod("process",
+                                       parameterTypes);
+                       Object[] methodArgs = { args };
+                       Object ret = method.invoke(agentCli, methodArgs);
 
-       private final static Options options;
+                       // Shutdown OSGi runtime
+                       framework.stop();
+                       framework.waitForStop(60 * 1000);
 
-       private final static String commandName = "slc";
+                       return ret.toString();
+               } catch (Exception e) {
+                       // Shutdown OSGi runtime
+                       if (framework != null)
+                               try {
+                                       framework.stop();
+                                       framework.waitForStop(15 * 1000);
+                               } catch (Exception silent) {
+                               }
+                       throw new RuntimeException("Cannot run SLC command line", e);
+               } finally {
 
-       static {
-               options = new Options();
-               options.addOption(modeOpt);
-               options.addOption(scriptOpt);
-               options.addOption(targetsOpt);
-               options.addOption(propertyOpt);
-               options.addOption(propertiesOpt);
-               options.addOption(runtimeOpt);
+               }
        }
 
        public static void main(String[] args) {
-               Mode mode = null;
-               Properties properties = new Properties();
-               String script = null;
-               String targets = null;
-               String runtimeStr = null;
-
                try {
-
-                       CommandLineParser clParser = new GnuParser();
-                       CommandLine cl = clParser.parse(options, args);
-
-                       // Mode
-                       String modeStr = cl.getOptionValue(modeOpt.getOpt());
-                       if (modeStr == null) {
-                               mode = Mode.single;
+                       // Prepare directories
+                       File executionDir = new File(System.getProperty("user.dir"));
+                       File slcDir;
+                       Boolean isTransient = false;
+                       if (isTransient) {
+                               File tempDir = new File(System.getProperty("java.io.tmpdir")
+                                               + "/" + System.getProperty("user.name"));
+                               slcDir = new File(tempDir, "slc-"
+                                               + UUID.randomUUID().toString());
+                               slcDir.mkdirs();
+                               System.setProperty("argeo.node.repo.configuration",
+                                               "osgibundle:repository-memory.xml");
                        } else {
-                               try {
-                                       mode = Mode.valueOf(modeStr);
-                               } catch (IllegalArgumentException e) {
-                                       throw new SlcException("Unrecognized mode '" + modeStr
-                                                       + "'", e);
+                               slcDir = findSlcDir(executionDir);
+                               if (slcDir == null) {
+                                       slcDir = new File(executionDir, slcDirName);
+                                       slcDir.mkdirs();
+                                       info("## Creating an SLC node at " + slcDir + " ...");
                                }
                        }
 
-                       // Script
-                       if (mode.equals(Mode.single)) {
-                               if (!cl.hasOption(scriptOpt.getOpt()))
-                                       throw new SlcException("Mode " + Mode.single
-                                                       + " requires option '" + scriptOpt.getLongOpt()
-                                                       + "'");
-                               script = cl.getOptionValue(scriptOpt.getOpt());
-
-                               // Targets
-                               if (cl.hasOption(targetsOpt.getOpt()))
-                                       targets = cl.getOptionValue(targetsOpt.getOpt());
-                       }
-
-                       // Properties
-                       if (cl.hasOption(propertiesOpt.getOpt())) {
-                               for (String propertyFile : cl.getOptionValues(propertiesOpt
-                                               .getOpt())) {
-                                       loadPropertyFile(properties, propertyFile);
-                               }
-                       }
-                       if (cl.hasOption(propertyOpt.getOpt())) {
-                               for (String property : cl.getOptionValues(propertyOpt.getOpt())) {
-                                       addProperty(properties, property);
-                               }
-                       }
-
-                       // Runtime
-                       if (cl.hasOption(runtimeOpt.getOpt())) {
-                               runtimeStr = cl.getOptionValue(runtimeOpt.getOpt());
-                       }
-               } catch (ParseException e) {
-                       System.err.println("Problem with command line arguments. "
-                                       + e.getMessage());
-                       badExit();
-               } catch (SlcException e) {
-                       System.err.println(e.getMessage());
-                       badExit();
+                       File dataDir = new File(slcDir, "data");
+                       if (!dataDir.exists())
+                               dataDir.mkdirs();
+
+                       File confDir = new File(slcDir, "conf");
+                       if (!confDir.exists())
+                               confDir.mkdirs();
+
+                       File modulesDir = new File(slcDir, "modules");
+
+                       // JAAS
+                       File jaasFile = new File(confDir, "jaas.config");
+                       if (!jaasFile.exists())
+                               copyResource("/org/argeo/slc/cli/jaas.config", jaasFile);
+                       System.setProperty("java.security.auth.login.config",
+                                       jaasFile.getCanonicalPath());
+
+                       // log4j
+                       File log4jFile = new File(confDir, "log4j.properties");
+                       if (!log4jFile.exists())
+                               copyResource("/org/argeo/slc/cli/log4j.properties", log4jFile);
+                       System.setProperty("log4j.configuration",
+                                       "file://" + log4jFile.getCanonicalPath());
+                       // Run as a privileged action
+                       LoginContext lc = new LoginContext(os);
+                       lc.login();
+
+                       Subject subject = Subject.getSubject(AccessController.getContext());
+                       Subject.doAs(subject, new SlcMain(args, confDir, dataDir,
+                                       modulesDir));
+
+                       if (args.length != 0)
+                               System.exit(0);
                } catch (Exception e) {
-                       System.err.println("Unexpected exception when bootstrapping.");
                        e.printStackTrace();
-                       badExit();
+                       System.exit(1);
                }
+       }
 
-               // Initializes logging and log arguments
-               initLogging(properties);
-               if (log.isDebugEnabled()) {
-                       log.debug("Mode: " + mode);
-                       if (runtimeStr != null)
-                               log.debug("Runtime: " + runtimeStr);
-                       log.debug("User properties: " + properties);
-                       if (script != null)
-                               log.debug("Script: " + script);
-                       if (targets != null)
-                               log.debug("Targets: " + targets);
+       /**
+        * Recursively look in parent directories for a directory named
+        * {@link #slcDirName}
+        */
+       protected static File findSlcDir(File currentDir) {
+               File slcDir = new File(currentDir, slcDirName);
+               // covers the use case of running from the home directory
+               if (slcDir.exists() && slcDir.isDirectory())
+                       return slcDir;
+               File parentDir = currentDir.getParentFile();
+               if (parentDir == null)
+                       return null;
+               try {
+                       // ~/.slc reserved for agent
+                       if (parentDir.getCanonicalPath().equals(homeDir.getCanonicalPath()))
+                               return null;
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot check home directory", e);
                }
+               return findSlcDir(parentDir);
+       }
 
-               // Execution
-               if (mode.equals(Mode.single)) {
+       protected static void copyResource(String resource, File targetFile) {
+               InputStream input = null;
+               FileOutputStream output = null;
+               try {
+                       input = SlcMain.class.getResourceAsStream(resource);
+                       output = new FileOutputStream(targetFile);
+                       byte[] buf = new byte[8192];
+                       while (true) {
+                               int length = input.read(buf);
+                               if (length < 0)
+                                       break;
+                               output.write(buf, 0, length);
+                       }
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot write " + resource + " file to "
+                                       + targetFile, e);
+               } finally {
                        try {
-                               // DefaultSlcRuntime runtime = new DefaultSlcRuntime();
-                               // FIXME: inject this more cleanly
-                               ClassLoader cl = Thread.currentThread().getContextClassLoader();
-                               Class clss = cl.loadClass("org.argeo.slc.ant.AntSlcRuntime");
-                               SlcRuntime<? extends SlcExecutionContext> runtime = (SlcRuntime<? extends SlcExecutionContext>) clss
-                                               .newInstance();
-                               runtime.executeScript(runtimeStr, script, targets, properties,
-                                               null, null);
-                               // System.exit(0);
-                       } catch (Exception e) {
-                               log.error("SLC client terminated with an error: ", e);
-                               System.exit(1);
+                               input.close();
+                       } catch (Exception ignore) {
                        }
-               }
-               // Agent
-               else if (mode.equals(Mode.agent)) {
-                       final ConfigurableApplicationContext applicationContext;
-                       if (runtimeStr == null) {
-                               applicationContext = new ClassPathXmlApplicationContext(
-                                               DEFAULT_AGENT_CONTEXT);
-                       } else {
-                               applicationContext = new FileSystemXmlApplicationContext(
-                                               runtimeStr);
+                       try {
+                               output.close();
+                       } catch (Exception ignore) {
                        }
-                       applicationContext.registerShutdownHook();
-                       applicationContext.start();
-                       log.info("SLC Agent context started.");
                }
-       }
 
-       public static void printUsage() {
-               new HelpFormatter().printHelp(commandName, options, true);
        }
 
-       private static String listModeValues() {
-               StringBuffer buf = new StringBuffer("");
-               for (Mode mode : Mode.values()) {
-                       buf.append(mode).append(", ");
-               }
-               String str = buf.toString();
-               // unsafe, but there will be at least one value in the enum
-               return str.substring(0, str.length() - 2);
+       protected static void info(Object msg) {
+               System.out.println(msg);
        }
 
-       protected static void addProperty(Properties properties, String property) {
-               int eqIndex = property.indexOf('=');
-               if (eqIndex == 0)
-                       throw new SlcException("Badly formatted property " + property);
-
-               if (eqIndex > 0) {
-                       String key = property.substring(0, eqIndex);
-                       String value = property.substring(eqIndex + 1);
-                       properties.setProperty(key, value);
-
-               } else {
-                       properties.setProperty(property, "true");
-               }
+       protected static void err(Object msg) {
+               System.err.println(msg);
        }
 
-       protected static void loadPropertyFile(Properties properties,
-                       String propertyFile) {
-               FileInputStream in = null;
-               try {
-                       in = new FileInputStream(propertyFile);
-                       properties.load(in);
-               } catch (Exception e) {
-                       throw new SlcException("Could not load proeprty file "
-                                       + propertyFile);
-               } finally {
-                       IOUtils.closeQuietly(in);
-               }
+       protected static void debug(Object msg) {
+               System.out.println(msg);
        }
 
-       private static void initLogging(Properties userProperties) {
-               System.setProperty("log4j.defaultInitOverride", "true");
-
-               // Add log4j user properties to System properties
-               for (Object obj : userProperties.keySet()) {
-                       String key = obj.toString();
-                       if (key.startsWith("log4j.")) {
-                               System.setProperty(key, userProperties.getProperty(key));
-                       }
-               }
-               Log4jUtils.initLog4j(System.getProperty("log4j.configuration",
-                               "classpath:" + BOOTSTRAP_LOG4J_CONFIG));
-               log = LogFactory.getLog(SlcMain.class);
+}
 
-       }
+// private String bundlesToInstall = System.getProperty("user.home")
+// +
+// "/dev/src/slc/dep/org.argeo.slc.dep.minimal/target/dependency;in=*.jar,"
+// + System.getProperty("user.home")
+// + "/dev/src/slc/demo/modules;in=*;ex=pom.xml;ex=.svn";
+
+// ServiceTracker agentTracker = new ServiceTracker(bundleContext,
+// "org.argeo.slc.execution.SlcAgentCli", null);
+// agentTracker.open();
+// final Object agentCli = agentTracker.waitForService(30 * 1000);
+// if (agentCli == null)
+// throw new RuntimeException("Cannot find SLC agent CLI");
+
+// protected class AgentCliCall implements PrivilegedAction<String> {
+// private final Object agentCli;
+//
+// public AgentCliCall(Object agentCli) {
+// super();
+// this.agentCli = agentCli;
+// }
+//
+// public String run() {
+// try {
+// Class<?>[] parameterTypes = { String[].class };
+// Method method = agentCli.getClass().getMethod("process",
+// parameterTypes);
+// Object[] methodArgs = { args };
+// Object ret = method.invoke(agentCli, methodArgs);
+// return ret.toString();
+// } catch (Exception e) {
+// throw new RuntimeException("Cannot run "
+// + Arrays.toString(args) + " on " + agentCli, e);
+// }
+// }
+//
+// }
 
-       private static void badExit() {
-               printUsage();
-               System.exit(1);
-       }
-}