org.eclipse.jetty.start.Config Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.start;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
/**
*
* It allows an application to be started with the command "java -jar start.jar"
.
*
*
*
* The behaviour of Main is controlled by the "org/eclipse/start/start.config"
file obtained as a resource
* or file. This can be overridden with the START system property. The format of each line in this file is:
*
*
*
* Each line contains entry in the format:
*
*
*
* SUBJECT [ [!] CONDITION [AND|OR] ]*
*
*
*
* where SUBJECT:
*
*
* - ends with
".class"
is the Main class to run.
* - ends with
".xml"
is a configuration file for the command line
* - ends with
"/"
is a directory from which to add all jar and zip files.
* - ends with
"/*"
is a directory from which to add all unconsidered jar and zip files.
* - ends with
"/**"
is a directory from which to recursively add all unconsidered jar and zip files.
* - Containing
=
are used to assign system properties.
* - Containing
~=
are used to assign start properties.
* - Containing
/=
are used to assign a canonical path.
* - all other subjects are treated as files to be added to the classpath.
*
*
*
* property expansion:
*
*
* ${name}
is expanded to a start property
* $(name)
is expanded to either a start property or a system property.
* - The start property
${version}
is defined as the version of the start.jar
*
*
*
* Files starting with "/"
are considered absolute, all others are relative to the home directory.
*
*
*
* CONDITION is one of:
*
*
* always
* never
* available classname
- true if class on classpath
* property name
- true if set as start property
* system name
- true if set as system property
* exists file
- true if file/dir exists
* java OPERATOR version
- java version compared to literal
* nargs OPERATOR number
- number of command line args compared to literal
* - OPERATOR := one of
"<"
,">"
,"<="
,">="
,
* "=="
,"!="
*
*
*
* CONDITIONS can be combined with AND
OR
or !
, with AND
being the
* assume operator for a list of CONDITIONS.
*
*
*
* Classpath operations are evaluated on the fly, so once a class or jar is added to the classpath, subsequent available
* conditions will see that class.
*
*
*
* The configuration file may be divided into sections with option names like: [ssl,default]
*
*
*
* Note: a special discovered section identifier [=path_to_directory/*]
is allowed to auto-create section
* IDs, based on directory names found in the path specified in the "path_to_directory/" part of the identifier.
*
*
*
* Clauses after a section header will only be included if they match one of the tags in the options property. By
* default options are set to "default,*" or the OPTIONS property may be used to pass in a list of tags, eg. :
*
*
*
* java -jar start.jar OPTIONS=jetty,jsp,ssl
*
*
*
* The tag '*' is always appended to the options, so any section with the * tag is always applied.
*
*
*
* The property map maintained by this class is static and shared between all instances in the same classloader
*
*/
public class Config
{
public static final String DEFAULT_SECTION = "";
static
{
String ver = System.getProperty("jetty.version", null);
if(ver == null) {
Package pkg = Config.class.getPackage();
if (pkg != null &&
"Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
(pkg.getImplementationVersion() != null))
{
ver = pkg.getImplementationVersion();
}
}
if (ver == null)
{
ver = "Unknown";
}
_version = ver;
}
/**
* Natural language sorting for key names.
*/
private final Comparator keySorter = new Comparator()
{
private final Collator collator = Collator.getInstance();
public int compare(String o1, String o2)
{
CollationKey key1 = collator.getCollationKey(o1);
CollationKey key2 = collator.getCollationKey(o2);
return key1.compareTo(key2);
}
};
private static final String _version;
private static boolean DEBUG = false;
private static final Map __properties = new HashMap();
private final Map _classpaths = new HashMap();
private final List _xml = new ArrayList();
private String _classname = null;
private int argCount = 0;
private final Set _activeOptions = new TreeSet(new Comparator()
{
// Make sure "*" is always at the end of the list
public int compare(String o1, String o2)
{
if ("*".equals(o1))
{
return 1;
}
if ("*".equals(o2))
{
return -1;
}
return o1.compareTo(o2);
}
});
private boolean addClasspathComponent(List sections, String component)
{
for (String section : sections)
{
Classpath cp = _classpaths.get(section);
if (cp == null)
cp = new Classpath();
boolean added = cp.addComponent(component);
_classpaths.put(section,cp);
if (!added)
{
// First failure means all failed.
return false;
}
}
return true;
}
private boolean addClasspathPath(List sections, String path)
{
for (String section : sections)
{
Classpath cp = _classpaths.get(section);
if (cp == null)
{
cp = new Classpath();
}
if (!cp.addClasspath(path))
{
// First failure means all failed.
return false;
}
_classpaths.put(section,cp);
}
return true;
}
private void addJars(List sections, File dir, boolean recurse) throws IOException
{
List entries = new ArrayList();
File[] files = dir.listFiles();
if (files == null)
{
// No files found, skip it.
return;
}
entries.addAll(Arrays.asList(files));
Collections.sort(entries,FilenameComparator.INSTANCE);
for (File entry : entries)
{
if (entry.isDirectory())
{
if (recurse)
addJars(sections,entry,recurse);
}
else
{
String name = entry.getName().toLowerCase(Locale.ENGLISH);
if (name.endsWith(".jar") || name.endsWith(".zip"))
{
String jar = entry.getCanonicalPath();
boolean added = addClasspathComponent(sections,jar);
debug((added?" CLASSPATH+=":" !") + jar);
}
}
}
}
private void close(InputStream stream)
{
if (stream == null)
return;
try
{
stream.close();
}
catch (IOException ignore)
{
/* ignore */
}
}
private void close(Reader reader)
{
if (reader == null)
return;
try
{
reader.close();
}
catch (IOException ignore)
{
/* ignore */
}
}
public static boolean isDebug()
{
return DEBUG;
}
public static void debug(String msg)
{
if (DEBUG)
{
System.err.println(msg);
}
}
public static void debug(Throwable t)
{
if (DEBUG)
{
t.printStackTrace(System.err);
}
}
private String expand(String s)
{
int i1 = 0;
int i2 = 0;
while (s != null)
{
i1 = s.indexOf("$(",i2);
if (i1 < 0)
break;
i2 = s.indexOf(")",i1 + 2);
if (i2 < 0)
break;
String name = s.substring(i1 + 2,i2);
String property = getProperty(name);
s = s.substring(0,i1) + property + s.substring(i2 + 1);
}
i1 = 0;
i2 = 0;
while (s != null)
{
i1 = s.indexOf("${",i2);
if (i1 < 0)
break;
i2 = s.indexOf("}",i1 + 2);
if (i2 < 0)
break;
String name = s.substring(i1 + 2,i2);
String property = getProperty(name);
s = s.substring(0,i1) + property + s.substring(i2 + 1);
}
return s;
}
/**
* Get the default classpath.
*
* @return the default classpath
*/
public Classpath getClasspath()
{
return _classpaths.get(DEFAULT_SECTION);
}
/**
* Get the active classpath, as dictated by OPTIONS= entries.
*
* @return the Active classpath
* @see #getCombinedClasspath(Collection)
*/
public Classpath getActiveClasspath()
{
return getCombinedClasspath(_activeOptions);
}
/**
* Get the combined classpath representing the default classpath plus all named sections.
*
* NOTE: the default classpath will be prepended, and the '*' classpath will be appended.
*
* @param optionIds
* the list of section ids to fetch
* @return the {@link Classpath} representing combination all of the selected sectionIds, combined with the default
* section id, and '*' special id.
*/
public Classpath getCombinedClasspath(Collection optionIds)
{
Classpath cp = new Classpath();
cp.overlay(_classpaths.get(DEFAULT_SECTION));
for (String optionId : optionIds)
{
Classpath otherCp = _classpaths.get(optionId);
if (otherCp == null)
{
throw new IllegalArgumentException("No such OPTIONS: " + optionId);
}
cp.overlay(otherCp);
}
cp.overlay(_classpaths.get("*"));
return cp;
}
public String getMainClassname()
{
return _classname;
}
public static void clearProperties()
{
__properties.clear();
}
public static Properties getProperties()
{
Properties properties = new Properties();
// Add System Properties First
Enumeration> ensysprop = System.getProperties().propertyNames();
while(ensysprop.hasMoreElements()) {
String name = (String)ensysprop.nextElement();
properties.put(name, System.getProperty(name));
}
// Add Config Properties Next (overwriting any System Properties that exist)
for (String key : __properties.keySet()) {
properties.put(key,__properties.get(key));
}
return properties;
}
public static String getProperty(String name)
{
if ("version".equalsIgnoreCase(name)) {
return _version;
}
// Search Config Properties First
if (__properties.containsKey(name)) {
return __properties.get(name);
}
// Return what exists in System.Properties otherwise.
return System.getProperty(name);
}
public static String getProperty(String name, String defaultValue)
{
// Search Config Properties First
if (__properties.containsKey(name))
return __properties.get(name);
// Return what exists in System.Properties otherwise.
return System.getProperty(name, defaultValue);
}
/**
* Get the classpath for the named section
*
* @param sectionId
* @return the classpath for the specified section id
*/
public Classpath getSectionClasspath(String sectionId)
{
return _classpaths.get(sectionId);
}
/**
* Get the list of section Ids.
*
* @return the set of unique section ids
*/
public Set getSectionIds()
{
Set ids = new TreeSet(keySorter);
ids.addAll(_classpaths.keySet());
return ids;
}
public List getXmlConfigs()
{
return _xml;
}
private boolean isAvailable(List options, String classname)
{
// Try default/parent class loader first.
try
{
Class.forName(classname);
return true;
}
catch (NoClassDefFoundError e)
{
debug(e);
}
catch (ClassNotFoundException e)
{
debug("ClassNotFoundException (parent class loader): " + classname);
}
// Try option classloaders instead
ClassLoader loader;
Classpath classpath;
for (String optionId : options)
{
classpath = _classpaths.get(optionId);
if (classpath == null)
{
// skip, no classpath
continue;
}
loader = classpath.getClassLoader();
try
{
loader.loadClass(classname);
return true;
}
catch (NoClassDefFoundError e)
{
debug(e);
}
catch (ClassNotFoundException e)
{
debug("ClassNotFoundException (section class loader: " + optionId + "): " + classname);
}
}
return false;
}
/**
* Parse the configuration
*
* @param buf
* @throws IOException
*/
public void parse(CharSequence buf) throws IOException
{
parse(new StringReader(buf.toString()));
}
/**
* Parse the configuration
*
* @param stream the stream to read from
* @throws IOException
*/
public void parse(InputStream stream) throws IOException
{
InputStreamReader reader = null;
try
{
reader = new InputStreamReader(stream);
parse(reader);
}
finally
{
close(reader);
}
}
/**
*/
public void parse(Reader reader) throws IOException
{
BufferedReader buf = null;
try
{
buf = new BufferedReader(reader);
List options = new ArrayList();
options.add(DEFAULT_SECTION);
_classpaths.put(DEFAULT_SECTION,new Classpath());
Version java_version = new Version(System.getProperty("java.version"));
Version ver = new Version();
String line = null;
while ((line = buf.readLine()) != null)
{
String trim = line.trim();
if (trim.length() == 0) // empty line
continue;
if (trim.startsWith("#")) // comment
continue;
// handle options
if (trim.startsWith("[") && trim.endsWith("]"))
{
String identifier = trim.substring(1,trim.length() - 1);
// Normal case: section identifier (possibly separated by commas)
options = Arrays.asList(identifier.split(","));
List option_ids=new ArrayList();
// Ensure section classpaths exist
for (String optionId : options)
{
if (optionId.charAt(0) == '=')
continue;
if (!_classpaths.containsKey(optionId))
_classpaths.put(optionId,new Classpath());
if (!option_ids.contains(optionId))
option_ids.add(optionId);
}
// Process Dynamic
for (String optionId : options)
{
if (optionId.charAt(0) != '=')
continue;
option_ids = processDynamicSectionIdentifier(optionId.substring(1),option_ids);
}
options = option_ids;
continue;
}
try
{
StringTokenizer st = new StringTokenizer(line);
String subject = st.nextToken();
boolean expression = true;
boolean not = false;
String condition = null;
// Evaluate all conditions
while (st.hasMoreTokens())
{
condition = st.nextToken();
if (condition.equalsIgnoreCase("!"))
{
not = true;
continue;
}
if (condition.equalsIgnoreCase("OR"))
{
if (expression)
break;
expression = true;
continue;
}
if (condition.equalsIgnoreCase("AND"))
{
if (!expression)
break;
continue;
}
boolean eval = true;
if (condition.equals("true") || condition.equals("always"))
{
eval = true;
}
else if (condition.equals("false") || condition.equals("never"))
{
eval = false;
}
else if (condition.equals("available"))
{
String class_to_check = st.nextToken();
eval = isAvailable(options,class_to_check);
}
else if (condition.equals("exists"))
{
try
{
eval = false;
File file = new File(expand(st.nextToken()));
eval = file.exists();
}
catch (Exception e)
{
debug(e);
}
}
else if (condition.equals("property"))
{
String property = getProperty(st.nextToken());
eval = property != null && property.length() > 0;
}
else if (condition.equals("system"))
{
String property = System.getProperty(st.nextToken());
eval = property != null && property.length() > 0;
}
else if (condition.equals("java"))
{
String operator = st.nextToken();
String version = st.nextToken();
ver.parse(version);
eval = (operator.equals("<") && java_version.compare(ver) < 0) || (operator.equals(">") && java_version.compare(ver) > 0)
|| (operator.equals("<=") && java_version.compare(ver) <= 0) || (operator.equals("=<") && java_version.compare(ver) <= 0)
|| (operator.equals("=>") && java_version.compare(ver) >= 0) || (operator.equals(">=") && java_version.compare(ver) >= 0)
|| (operator.equals("==") && java_version.compare(ver) == 0) || (operator.equals("!=") && java_version.compare(ver) != 0);
}
else if (condition.equals("nargs"))
{
String operator = st.nextToken();
int number = Integer.parseInt(st.nextToken());
eval = (operator.equals("<") && argCount < number) || (operator.equals(">") && argCount > number)
|| (operator.equals("<=") && argCount <= number) || (operator.equals("=<") && argCount <= number)
|| (operator.equals("=>") && argCount >= number) || (operator.equals(">=") && argCount >= number)
|| (operator.equals("==") && argCount == number) || (operator.equals("!=") && argCount != number);
}
else
{
System.err.println("ERROR: Unknown condition: " + condition);
eval = false;
}
expression &= not?!eval:eval;
not = false;
}
String file = expand(subject);
debug((expression?"T ":"F ") + line);
if (!expression)
continue;
// Setting of a start property
if (subject.indexOf("~=") > 0)
{
int i = file.indexOf("~=");
String property = file.substring(0,i);
String value = fixPath(file.substring(i + 2));
debug(" " + property + "~=" + value);
setProperty(property,value);
continue;
}
// Setting of start property with canonical path
if (subject.indexOf("/=") > 0)
{
int i = file.indexOf("/=");
String property = file.substring(0,i);
String value = fixPath(file.substring(i + 2));
String canonical = new File(value).getCanonicalPath();
debug(" " + property + "/=" + value + "==" + canonical);
setProperty(property,canonical);
continue;
}
// Setting of system property
if (subject.indexOf("=") > 0)
{
int i = file.indexOf("=");
String property = file.substring(0,i);
String value = fixPath(file.substring(i + 1));
debug(" " + property + "=" + value);
System.setProperty(property,value);
continue;
}
// Add all unconsidered JAR and ZIP files to classpath
if (subject.endsWith("/*"))
{
// directory of JAR files - only add jars and zips within the directory
File dir = new File(fixPath(file.substring(0,file.length() - 1)));
addJars(options,dir,false);
continue;
}
// Recursively add all unconsidered JAR and ZIP files to classpath
if (subject.endsWith("/**"))
{
//directory hierarchy of jar files - recursively add all jars and zips in the hierarchy
File dir = new File(fixPath(file.substring(0,file.length() - 2)));
addJars(options,dir,true);
continue;
}
// Add raw classpath directory to classpath
if (subject.endsWith("/"))
{
// class directory
File cd = new File(fixPath(file));
String d = cd.getCanonicalPath();
boolean added = addClasspathComponent(options,d);
debug((added?" CLASSPATH+=":" !") + d);
continue;
}
// Add XML configuration
if (subject.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
{
// Config file
File f = new File(fixPath(file));
if (f.exists())
_xml.add(f.getCanonicalPath());
debug(" ARGS+=" + f);
continue;
}
// Set the main class to execute (overrides any previously set)
if (subject.toLowerCase(Locale.ENGLISH).endsWith(".class"))
{
// Class
String cn = expand(subject.substring(0,subject.length() - 6));
if (cn != null && cn.length() > 0)
{
debug(" CLASS=" + cn);
_classname = cn;
}
continue;
}
// Add raw classpath entry
if (subject.toLowerCase(Locale.ENGLISH).endsWith(".path"))
{
// classpath (jetty.class.path?) to add to runtime classpath
String cn = expand(subject.substring(0,subject.length() - 5));
if (cn != null && cn.length() > 0)
{
debug(" PATH=" + cn);
addClasspathPath(options,cn);
}
continue;
}
// single JAR file
File f = new File(fixPath(file));
if (f.exists())
{
String d = f.getCanonicalPath();
boolean added = addClasspathComponent(options,d);
if (!added)
{
added = addClasspathPath(options,expand(subject));
}
debug((added?" CLASSPATH+=":" !") + d);
}
}
catch (Exception e)
{
System.err.println("on line: '" + line + "'");
e.printStackTrace();
}
}
}
finally
{
close(buf);
}
}
private List processDynamicSectionIdentifier(String dynamicPathId,List sections) throws IOException
{
String rawPath;
boolean deep;
if (dynamicPathId.endsWith("/*"))
{
deep=false;
rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 1));
}
else if (dynamicPathId.endsWith("/**"))
{
deep=true;
rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 2));
}
else
{
String msg = "Illegal dynamic path [" + dynamicPathId + "]";
throw new IOException(msg);
}
File parentDir = new File(expand(rawPath));
if (!parentDir.exists())
return sections;
debug("dynamic: " + parentDir);
File dirs[] = parentDir.listFiles(new FileFilter()
{
public boolean accept(File path)
{
return path.isDirectory();
}
});
List dyn_sections = new ArrayList();
List super_sections = new ArrayList();
if (sections!=null)
super_sections.addAll(sections);
for (File dir : dirs)
{
String id = dir.getName();
if (!_classpaths.keySet().contains(id))
_classpaths.put(id, new Classpath());
dyn_sections.clear();
if (sections!=null)
dyn_sections.addAll(sections);
dyn_sections.add(id);
super_sections.add(id);
debug("dynamic: " + dyn_sections);
addJars(dyn_sections,dir,deep);
}
return super_sections;
}
private String fixPath(String path)
{
return path.replace('/',File.separatorChar);
}
public void parse(URL url) throws IOException
{
InputStream stream = null;
InputStreamReader reader = null;
try
{
stream = url.openStream();
reader = new InputStreamReader(stream);
parse(reader);
}
finally
{
close(reader);
close(stream);
}
}
public void setArgCount(int argCount)
{
this.argCount = argCount;
}
public void setProperty(String name, String value)
{
if (name.equals("DEBUG"))
{
DEBUG = Boolean.parseBoolean(value);
if (DEBUG)
{
System.setProperty("org.eclipse.jetty.util.log.stderr.DEBUG","true");
System.setProperty("org.eclipse.jetty.start.DEBUG","true");
}
}
if (name.equals("OPTIONS"))
{
_activeOptions.clear();
String ids[] = value.split(",");
for (String id : ids)
{
addActiveOption(id);
}
}
__properties.put(name,value);
}
public void addActiveOption(String option)
{
_activeOptions.add(option);
__properties.put("OPTIONS",join(_activeOptions,","));
}
public Set getActiveOptions()
{
return _activeOptions;
}
public void removeActiveOption(String option)
{
_activeOptions.remove(option);
__properties.put("OPTIONS",join(_activeOptions,","));
}
private String join(Collection> coll, String delim)
{
StringBuffer buf = new StringBuffer();
Iterator> i = coll.iterator();
boolean hasNext = i.hasNext();
while (hasNext)
{
buf.append(String.valueOf(i.next()));
hasNext = i.hasNext();
if (hasNext)
buf.append(delim);
}
return buf.toString();
}
}