/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved. */ package org.opensolaris.opengrok.configuration; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.logging.Level; import java.util.logging.Logger; import org.opensolaris.opengrok.history.HistoryGuru; import org.opensolaris.opengrok.util.Executor; import org.opensolaris.opengrok.util.IOUtils; /** * The RuntimeEnvironment class is used as a placeholder for the current * configuration this execution context (classloader) is using. */ public final class RuntimeEnvironment { private Configuration config; private final ThreadLocal threadConfig; private static final Logger log = Logger.getLogger(RuntimeEnvironment.class.getName()); private static RuntimeEnvironment instance = new RuntimeEnvironment(); private static Boolean oldVM; /** * Get the one and only instance of the RuntimeEnvironment * @return the one and only instance of the RuntimeEnvironment */ public static RuntimeEnvironment getInstance() { return instance; } /** * Creates a new instance of RuntimeEnvironment. Private to ensure a * singleton pattern. */ private RuntimeEnvironment() { config = new Configuration(); threadConfig = new ThreadLocal() { @SuppressWarnings("synthetic-access") @Override protected Configuration initialValue() { return config; } }; } /** * Makes testing easier by being able to drop the current env and replace it * with a virgin one. */ RuntimeEnvironment resetForTest() { threadConfig.remove(); instance = new RuntimeEnvironment(); return instance; } /** * Check, whether the running JVM is older than version 1.7 . * @return {@code true} if older than 1.7 */ public static final boolean isOldJVM() { if (oldVM == null) { String[] s = System.getProperty("java.specification.version", "1.6") .split("\\."); int minor = 0; int major = 0; try { major = Integer.parseInt(s[0], 10); minor = Integer.parseInt(s[1], 10); } catch (Exception e) { // ignore } oldVM = Boolean.valueOf(major <= 1 && minor < 7); } return oldVM.booleanValue(); } /** * Register this thread in the thread/configuration map (so that all * subsequent calls to the RuntimeEnvironment from this thread will use * the same configuration * @return this instance */ public static RuntimeEnvironment register() { instance.threadConfig.set(instance.config); return instance; } /** * Validate that I have a Exuberant ctags program I may use. * @return {@code true} if Exuberant ctags could be run. */ public static boolean validateExuberantCtags() { boolean ret = true; Executor executor = new Executor(new String[] { getConfig().getCtags(), "--version"}); executor.exec(false); String output = executor.getOutputString(); if (output == null || output.indexOf("Exuberant Ctags") == -1) { log.warning("No Exuberant Ctags found in PATH!\n" + "(tried running '" + getConfig().getCtags() + "')\n" + "Please use option -c to specify path to a good Exuberant Ctags program\n"+ "Or set it in java system property " + Configuration.CTAGS_CMD_PROPERTY_KEY); ret = false; } return ret; } /** * Read an configuration file and set it as the current configuration. * @param file the file to read * @throws IOException if an error occurs */ public static void readConfig(File file) throws IOException { setConfig(Configuration.read(file)); } /** * Write the current configuration to a file * @param file the file to write the configuration into * @throws IOException if an error occurs */ public static void writeConfig(File file) throws IOException { getConfig().write(file); } /** * Write the current configuration to a socket * @param host the host address to receive the configuration * @param port the port to use on the host * @throws IOException if an error occurs */ public static void writeConfig(InetAddress host, int port) throws IOException { Socket sock = new Socket(host, port); XMLEncoder e = new XMLEncoder(sock.getOutputStream()); e.writeObject(getConfig()); e.close(); IOUtils.close(sock); } /** * Send the current configuration to the web application [server]. * @throws IOException */ protected void writeConfig() throws IOException { writeConfig(configServerSocket.getInetAddress(), configServerSocket.getLocalPort()); } /** * Set the current configuration for this instance to the given parameter. * Involves invalidating known repositories of the {@link HistoryGuru}. * @param config configuration to set. */ public static void setConfig(Configuration config) { instance.config = config; register(); HistoryGuru.getInstance() .invalidateRepositories(config.getRepositories()); } /** * Get the current configuration for this instance. * @return the current configuration. */ public static Configuration getConfig() { return instance.threadConfig.get(); } private ServerSocket configServerSocket; /** * Try to stop the configuration listener thread */ public static void stopConfigListenerThread() { IOUtils.close(instance.configServerSocket); } /** * Start a thread to listen on a socket to receive new configurations * to use. * @param endpoint The socket address to listen on * @return true if the endpoint was available (and the thread was started) */ public static boolean startConfigListenerThread(SocketAddress endpoint) { boolean ret = false; try { instance.configServerSocket = new ServerSocket(); instance.configServerSocket.bind(endpoint); ret = true; final ServerSocket sock = instance.configServerSocket; Thread t = new Thread(new Runnable() { @SuppressWarnings("synthetic-access") @Override public void run() { ByteArrayOutputStream bos = new ByteArrayOutputStream(1<<13); while (!sock.isClosed()) { Socket s = null; BufferedInputStream in = null; try { s = sock.accept(); bos.reset(); log.info("Re-configure request from " + s.getInetAddress().getHostAddress()); in = new BufferedInputStream(s.getInputStream()); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) { bos.write(buf, 0, len); } buf = bos.toByteArray(); if (log.isLoggable(Level.FINE)) { log.fine("New config: \n" + new String(buf)); } XMLDecoder d = new XMLDecoder(new ByteArrayInputStream(buf)); Object obj = d.readObject(); d.close(); if (obj instanceof Configuration) { setConfig((Configuration)obj); log.log(Level.INFO, "Configuration updated: {0}", getConfig().getSourceRoot()); } } catch (IOException e) { log.warning("Error reading config file: " + e.getMessage()); log.log(Level.FINE, "run", e); } catch (RuntimeException e) { log.warning("Error parsing config file: " + e.getMessage()); log.log(Level.FINE, "run", e); } finally { IOUtils.close(s); IOUtils.close(in); } } } }); t.start(); } catch (UnknownHostException ex) { log.log(Level.FINE,"Problem resolving sender", ex); } catch (IOException ex) { log.log(Level.FINE,"I/O error when waiting for config", ex); } if (!ret && instance.configServerSocket != null) { IOUtils.close(instance.configServerSocket); } return ret; } }