/*
* Copyright (c) 2009-present the original author or authors.
*
* 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 com.planet57.gshell.launcher;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Bootstrap configuration.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class Configuration
{
public static final String SHELL_HOME_DETECTED = "shell.home.detected";
public static final String SHELL_HOME = "shell.home";
public static final String SHELL_ETC = "shell.etc";
public static final String SHELL_LIB = "shell.lib";
public static final String SHELL_PROGRAM = "shell.program";
public static final String SHELL_VERSION = "shell.version";
public static final String SHELL_MAIN = "shell.main";
public static final String DEFAULT_PROPERTIES = "default.properties";
public static final String BOOTSTRAP_PROPERTIES = "bootstrap.properties";
public static final String BOOTSTRAP_JAR = "bootstrap.jar";
public static final int SUCCESS_EXIT_CODE = 0;
public static final int FAILURE_EXIT_CODE = 100;
private static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
private Properties props;
private Properties loadProperties() throws Exception {
Properties props = new Properties();
URL defaults = getClass().getResource(DEFAULT_PROPERTIES);
if (defaults == null) {
// Should never happen
throw new RuntimeException("Missing resource: " + DEFAULT_PROPERTIES);
}
mergeProperties(props, defaults);
URL bootstrap = getClass().getClassLoader().getResource(BOOTSTRAP_PROPERTIES);
if (bootstrap == null) {
// Happens when assembly forgets to install the bootstrap properties file
throw new RuntimeException("Missing resource: " + BOOTSTRAP_PROPERTIES);
}
mergeProperties(props, bootstrap);
return props;
}
private void mergeProperties(final Properties props, final URL url) throws IOException {
Log.debug("Merging properties from: ", url);
try (InputStream input = url.openStream()) {
props.load(input);
}
}
/**
* Detect the home directory, which is expected to be <tt>../../</tt> from the location of the jar containing this
* class.
*/
private File detectHomeDir() throws Exception {
String path = getClass().getProtectionDomain().getCodeSource().getLocation().getFile();
path = URLDecoder.decode(path, "UTF-8");
return new File(path).getParentFile().getParentFile().getCanonicalFile();
}
public void configure() throws Exception {
Log.debug("Configuring");
props = loadProperties();
props.setProperty(SHELL_HOME_DETECTED, detectHomeDir().getPath());
if (Log.DEBUG) {
Log.debug("Properties:");
for (Map.Entry entry : props.entrySet()) {
Log.debug(" ", entry.getKey(), "=", entry.getValue());
}
}
requireProperty(SHELL_MAIN);
// Export some configuration
setSystemProperty(SHELL_HOME, getPropertyAsFile(SHELL_HOME).getAbsolutePath());
setSystemProperty(SHELL_PROGRAM, requireProperty(SHELL_PROGRAM));
setSystemProperty(SHELL_VERSION, requireProperty(SHELL_VERSION));
}
private void setSystemProperty(final String name, final String value) {
assert name != null;
assert value != null;
Log.debug(name, ": ", value);
System.setProperty(name, value);
}
private void ensureConfigured() {
if (props == null) {
throw new IllegalStateException("Not configured");
}
}
/**
* Get the value of a property, checking system properties, then configuration properties and evaluating the result.
*/
@Nullable
private String getProperty(final String name) {
assert name != null;
ensureConfigured();
return evaluate(System.getProperty(name, props.getProperty(name)));
}
@Nullable
private String evaluate(@Nullable String input) {
if (input != null) {
Matcher matcher = PATTERN.matcher(input);
while (matcher.find()) {
Object rep = props.get(matcher.group(1));
if (rep != null) {
input = input.replace(matcher.group(0), rep.toString());
matcher.reset(input);
}
}
}
return input;
}
private File getPropertyAsFile(final String name) {
String path = requireProperty(name);
return new File(path).getAbsoluteFile();
}
private String requireProperty(final String name) {
String value = getProperty(name);
if (value == null) {
throw new RuntimeException("Missing required property: " + name);
}
return value;
}
public List<URL> getClassPath() throws Exception {
ensureConfigured();
List<URL> classPath = new ArrayList<>();
classPath.add(getPropertyAsFile(SHELL_ETC).toURI().toURL());
File dir = getPropertyAsFile(SHELL_LIB);
Log.debug("Finding jars under: ", dir);
File[] files = dir.listFiles(file -> {
assert file != null;
String name = file.getName().toLowerCase();
return !name.equals(BOOTSTRAP_JAR) && file.isFile() && name.endsWith(".jar");
});
if (files == null) {
throw new RuntimeException("No jars found under: " + dir);
}
for (File file : files) {
classPath.add(file.toURI().toURL());
}
return classPath;
}
public String getMainClass() {
return requireProperty(SHELL_MAIN);
}
public int getSuccessExitCode() {
return SUCCESS_EXIT_CODE;
}
public int getFailureExitCode() {
return FAILURE_EXIT_CODE;
}
}