/* * Copyright (C) 2012 Google Inc. * * 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 interactivespaces.launcher; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.management.ManagementFactory; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.FileLock; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * The initial launcher for Interactive Spaces. * * @author Keith M. Hughes */ public class InteractiveSpacesLauncher { /** * The name of the class which bootstraps the Interactoive Spaces framework. */ private static final String CLASSNAME_INTERACTIVESPACES_FRAMEWORK_BOOTSTRAP = "interactivespaces.launcher.bootstrap.InteractiveSpacesFrameworkBootstrap"; /** * The run folder in the directory where the container is installed. */ private static final String RUN_DIR_PATH = "run"; /** * The name of the PID file for the container. */ private static final String FILENAME_INTERACTIVESPACES_PID = "interactivespaces.pid"; /** * The file extension used for files which give container extensions. */ private static final String EXTENSION_FILE_EXTENSION = ".ext"; /** * The keyword header for a package line on an extensions file. */ public static final String EXTENSION_FILE_PATH_KEYWORD = "path:"; /** * The length of the keyword header for a package line on an extensions file. */ public static final int EXTENSION_FILE_PATH_KEYWORD_LENGTH = EXTENSION_FILE_PATH_KEYWORD.length(); /** * The subdirectory which contains the system files. */ private static final String SPACES_LIB_SYSTEM_JAVA = "lib/system/java"; /** * The subdirectory which contains the environment files relative to the config folder. */ private static final String SPACES_CONFIG_ENVIRONMENT = "environment"; /** * Command line argument prefix for specifying a specific runtime path. This should match the value of * {@code InteractiveSpacesFrameworkBootstrap.ARGS_RUNTIME_PREFIX}, but can't be a shared variable because of * package dependency considerations. */ private static final String COMMAND_LINE_RUNTIME_PREFIX = "--runtime="; /** * Command line argument prefix for specifying a specific config path. This should match the value of * {@code InteractiveSpacesFrameworkBootstrap.ARGS_CONFIG_PREFIX}, but can't be a shared variable because of * package dependency considerations. */ private static final String COMMAND_LINE_CONFIG_PREFIX = "--config="; /** * The classloader for starting Interactive Spaces. */ private ClassLoader classLoader; /** * Path to the runtime directory, which is the root for dynamic configuration. Can be set by a command line flag. */ private String runtimePath = "."; /** * Path to the config directory, which is the root for dynamic configuration. Can be set by a command line flag. */ private String configPath = "config"; /** * The file which gives the process ID for the Interactive Spaces process. */ private File pidFile; /** * The main method for Interactive Spaces. * * @param args * the command line arguments * * @throws Exception * fall down go boom */ public static void main(String[] args) throws Exception { InteractiveSpacesLauncher launcher = new InteractiveSpacesLauncher(); launcher.launch(args); } /** * Launch Interactive Spaces. * * @param args * the command line arguments */ public void launch(String[] args) { // argList needs to be mutable because it is modified by some components downstream. List<String> argList = new ArrayList<String>(); Collections.addAll(argList, args); processLauncherCommandArgs(argList); if (writePid()) { createClassLoader(); boostrap(argList); } else { System.err.format("InteractiveSpaces component already running. Lock found on %s\n", pidFile.getAbsolutePath()); } } /** * Process any command line args for the launcher. This leaves the arguments in-place in case they're * needed downstream. * * @param args * list of arguments */ private void processLauncherCommandArgs(List<String> args) { for (String thisArg : args) { if (thisArg.startsWith(COMMAND_LINE_RUNTIME_PREFIX)) { runtimePath = thisArg.substring(COMMAND_LINE_RUNTIME_PREFIX.length()); System.out.println("Setting runtime path to " + runtimePath); } else if (thisArg.startsWith(COMMAND_LINE_CONFIG_PREFIX)) { configPath = thisArg.substring(COMMAND_LINE_CONFIG_PREFIX.length()); System.out.println("Setting config path to " + configPath); } } } /** * Create the classloader to use to start the system. */ private void createClassLoader() { List<URL> urls = new ArrayList<URL>(); collectSystemLibClasspath(urls); addExtensionsToClasspath(urls); classLoader = new URLClassLoader(urls.toArray(new URL[0])); } /** * Get the classpath to be used for starting the system. * * @param classpath * the classpath list */ private void collectSystemLibClasspath(List<URL> classpath) { File systemDirectory = new File(SPACES_LIB_SYSTEM_JAVA); File[] files = systemDirectory.listFiles(); if (files != null) { for (File file : files) { if (file.getName().endsWith(EXTENSION_FILE_EXTENSION)) { continue; } try { URL url = file.toURI().toURL(); classpath.add(url); } catch (MalformedURLException e) { e.printStackTrace(); } } } if (classpath.isEmpty()) { System.err.format("No bootstrap files found in %s", systemDirectory.getAbsolutePath()); } } /** * Add any extension classpath additions to the classpath. * * @param classpath * the classpath */ private void addExtensionsToClasspath(List<URL> classpath) { File extensionsDirectory = new File(new File(configPath), SPACES_CONFIG_ENVIRONMENT); File[] extensionFiles = extensionsDirectory.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(EXTENSION_FILE_EXTENSION); } }); if (extensionFiles == null) { return; } for (File extensionFile : extensionFiles) { processExtensionFile(classpath, extensionFile); } } /** * process an extension file. * * @param urls * the collection of urls to be placed on the classpath at boot * * @param extensionFile * the extension file to process */ private void processExtensionFile(List<URL> urls, File extensionFile) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(extensionFile)); String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (!line.isEmpty()) { int pos = line.indexOf(EXTENSION_FILE_PATH_KEYWORD); if (pos == 0 && line.length() > EXTENSION_FILE_PATH_KEYWORD_LENGTH) { File path = new File(line.substring(EXTENSION_FILE_PATH_KEYWORD_LENGTH).trim()); urls.add(path.toURI().toURL()); } } } } catch (Exception e) { System.out.format("Error while processing extensions file %s\n", extensionFile); e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // Don't care. } } } } /** * Bootstrap the framework. * * @param argList * the command line arguments */ private void boostrap(List<String> argList) { try { Class<?> bootstrapClass = classLoader.loadClass(CLASSNAME_INTERACTIVESPACES_FRAMEWORK_BOOTSTRAP); Object bootstrapInstance = bootstrapClass.newInstance(); Method boostrapMethod = bootstrapClass.getMethod("boot", List.class); boostrapMethod.invoke(bootstrapInstance, argList); } catch (Exception e) { System.err.println("Could not create bootstrapper"); e.printStackTrace(System.err); } } /** * Try and write the pid file. * * @return {@code false} if there was an error creating the pid file */ private boolean writePid() { File runDirectory = new File(runtimePath, RUN_DIR_PATH); if (!runDirectory.exists()) { if (!runDirectory.mkdir()) { System.err.format("Could not create run directory %s\n", runDirectory); return false; } } pidFile = new File(runDirectory, FILENAME_INTERACTIVESPACES_PID); try { RandomAccessFile pidRaf = new RandomAccessFile(pidFile, "rw"); FileLock fl = pidRaf.getChannel().tryLock(0, Long.MAX_VALUE, false); if (fl != null) { writePidFile(pidRaf); pidFile.deleteOnExit(); return true; } else { // someone else has the lock return false; } } catch (Exception e) { e.printStackTrace(); return false; } } /** * Attempt to write into the PID file. * * @param pidRaf * the file reference * * @return {@code true} if the write was successful */ private boolean writePidFile(RandomAccessFile pidRaf) { try { pidRaf.writeChars(Integer.toString(getPid())); return true; } catch (IOException e) { e.printStackTrace(); return false; } } /** * @return PID of node process if available, throws * {@link UnsupportedOperationException} otherwise. */ private int getPid() { // Java has no standard way of getting PID. MF.getName() // returns '1234@localhost'. try { String mxName = ManagementFactory.getRuntimeMXBean().getName(); int idx = mxName.indexOf('@'); if (idx > 0) { try { return Integer.parseInt(mxName.substring(0, idx)); } catch (NumberFormatException e) { return 0; } } } catch (NoClassDefFoundError unused) { // Android does not support ManagementFactory. Try to get the PID on // Android. try { return (Integer) Class.forName("android.os.Process").getMethod("myPid").invoke(null); } catch (Exception unused1) { // Ignore this exception and fall through to the // UnsupportedOperationException. } } throw new UnsupportedOperationException(); } }