/*
* 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<Configuration> 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<Configuration>() {
@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;
}
}