284510 Enhance jetty-start for diagnosis and unit testing
Merging in changes made on /jetty-start-enhancement/ branch git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@587 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
parent
778489d7cf
commit
bb50e1bac8
|
@ -4,6 +4,7 @@ jetty-7.0.0.RC2-SNAPSHOT
|
|||
+ 283818 fixed merge of forward parameters
|
||||
+ backport jetty-8 annotation parsing to jetty-7
|
||||
+ Disassociate method on IdentityService
|
||||
+ 284510 Enhance jetty-start for diagnosis and unit testing
|
||||
|
||||
jetty-7.0.0.RC1 15 June 2009
|
||||
+ JETTY-1066 283357 400 response for bad URIs
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.start;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
@ -26,11 +27,10 @@ import java.util.Vector;
|
|||
|
||||
/**
|
||||
* Class to handle CLASSPATH construction
|
||||
*
|
||||
*/
|
||||
public class Classpath {
|
||||
|
||||
Vector _elements = new Vector();
|
||||
private final Vector<File> _elements = new Vector<File>();
|
||||
|
||||
public Classpath()
|
||||
{}
|
||||
|
@ -42,7 +42,12 @@ public class Classpath {
|
|||
|
||||
public File[] getElements()
|
||||
{
|
||||
return (File[])_elements.toArray(new File[_elements.size()]);
|
||||
return _elements.toArray(new File[_elements.size()]);
|
||||
}
|
||||
|
||||
public int count()
|
||||
{
|
||||
return _elements.size();
|
||||
}
|
||||
|
||||
public boolean addComponent(String component)
|
||||
|
@ -92,18 +97,28 @@ public class Classpath {
|
|||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(PrintStream out)
|
||||
{
|
||||
int i = 0;
|
||||
for (File element : _elements)
|
||||
{
|
||||
out.printf("%2d: %s\n",i++,element.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuffer cp = new StringBuffer(1024);
|
||||
int cnt = _elements.size();
|
||||
if (cnt >= 1) {
|
||||
cp.append( ((File)(_elements.elementAt(0))).getPath() );
|
||||
cp.append( ((_elements.elementAt(0))).getPath() );
|
||||
}
|
||||
for (int i=1; i < cnt; i++) {
|
||||
cp.append(File.pathSeparatorChar);
|
||||
cp.append( ((File)(_elements.elementAt(i))).getPath() );
|
||||
cp.append( ((_elements.elementAt(i))).getPath() );
|
||||
}
|
||||
return cp.toString();
|
||||
}
|
||||
|
@ -113,7 +128,7 @@ public class Classpath {
|
|||
URL[] urls = new URL[cnt];
|
||||
for (int i=0; i < cnt; i++) {
|
||||
try {
|
||||
String u=((File)(_elements.elementAt(i))).toURL().toString();
|
||||
String u=((_elements.elementAt(i))).toURL().toString();
|
||||
urls[i] = new URL(encodeFileURL(u));
|
||||
} catch (MalformedURLException e) {}
|
||||
}
|
||||
|
@ -128,7 +143,7 @@ public class Classpath {
|
|||
return new Loader(urls, parent);
|
||||
}
|
||||
|
||||
private class Loader extends URLClassLoader
|
||||
private static class Loader extends URLClassLoader
|
||||
{
|
||||
String name;
|
||||
|
||||
|
@ -138,6 +153,7 @@ public class Classpath {
|
|||
name = "StartLoader"+Arrays.asList(urls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return name;
|
||||
|
@ -189,8 +205,8 @@ public class Classpath {
|
|||
}
|
||||
}
|
||||
buf.append('%');
|
||||
buf.append(Integer.toHexString((0xf0&(int)b)>>4));
|
||||
buf.append(Integer.toHexString((0x0f&(int)b)));
|
||||
buf.append(Integer.toHexString((0xf0&b)>>4));
|
||||
buf.append(Integer.toHexString((0x0f&b)));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -198,4 +214,29 @@ public class Classpath {
|
|||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay another classpath, copying its elements into place on this Classpath, while eliminating duplicate entries
|
||||
* on the classpath.
|
||||
*
|
||||
* @param cpOther
|
||||
* the other classpath to overlay
|
||||
*/
|
||||
public void overlay(Classpath cpOther)
|
||||
{
|
||||
for (File otherElement : cpOther._elements)
|
||||
{
|
||||
if (this._elements.contains(otherElement))
|
||||
{
|
||||
// Skip duplicate entries
|
||||
continue;
|
||||
}
|
||||
this._elements.add(otherElement);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return (_elements == null) || (_elements.isEmpty());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,816 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) Webtide LLC
|
||||
// ------------------------------------------------------------------------
|
||||
// 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.apache.org/licenses/LICENSE-2.0.txt
|
||||
//
|
||||
// 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.security.Policy;
|
||||
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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* It allows an application to be started with the command <code>"java -jar start.jar"</code>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The behaviour of Main is controlled by the <code>"org/eclipse/start/start.config"</code> 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:
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Each line contains entry in the format:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* SUBJECT [ [!] CONDITION [AND|OR] ]*
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* where SUBJECT:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>ends with <code>".class"</code> is the Main class to run.</li>
|
||||
* <li>ends with <code>".xml"</code> is a configuration file for the command line</li>
|
||||
* <li>ends with <code>"/"</code> is a directory from which to add all jar and zip files.</li>
|
||||
* <li>ends with <code>"/*"</code> is a directory from which to add all unconsidered jar and zip files.</li>
|
||||
* <li>ends with <code>"/**"</code> is a directory from which to recursively add all unconsidered jar and zip files.</li>
|
||||
* <li>Containing <code>=</code> are used to assign system properties.</li>
|
||||
* <li>Containing <code>~=</code> are used to assign start properties.</li>
|
||||
* <li>Containing <code>/=</code> are used to assign a canonical path.</li>
|
||||
* <li>all other subjects are treated as files to be added to the classpath.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* property expansion:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><code>${name}</code> is expanded to a start property</li>
|
||||
* <li><code>$(name)</code> is expanded to either a start property or a system property.</li>
|
||||
* <li>The start property <code>${version}</code> is defined as the version of the start.jar</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Files starting with <code>"/"</code> are considered absolute, all others are relative to the home directory.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* CONDITION is one of:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><code>always</code></li>
|
||||
* <li><code>never</code></li>
|
||||
* <li><code>available classname</code> - true if class on classpath</li>
|
||||
* <li><code>property name</code> - true if set as start property</li>
|
||||
* <li><code>system name</code> - true if set as system property</li>
|
||||
* <li><code>exists file</code> - true if file/dir exists</li>
|
||||
* <li><code>java OPERATOR version</code> - java version compared to literal</li>
|
||||
* <li><code>nargs OPERATOR number</code> - number of command line args compared to literal</li>
|
||||
* <li>OPERATOR := one of <code>"<"</code>,<code>">"</code>,<code>"<="</code>,<code>">="</code>,
|
||||
* <code>"=="</code>,<code>"!="</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* CONDITIONS can be combined with <code>AND</code> <code>OR</code> or <code>!</code>, with <code>AND</code> being the
|
||||
* assume operator for a list of CONDITIONS.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The configuration file may be divided into sections with option names like: [ssl,default]
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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. :
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* java -jar start.jar OPTIONS=jetty,jsp,ssl
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The tag '*' is always appended to the options, so any section with the * tag is always applied.
|
||||
* </p>
|
||||
*/
|
||||
public class Config
|
||||
{
|
||||
public static final String DEFAULT_SECTION = "";
|
||||
static
|
||||
{
|
||||
Package pkg = Config.class.getPackage();
|
||||
if (pkg != null && (pkg.getImplementationVersion() != null))
|
||||
_version = pkg.getImplementationVersion();
|
||||
else
|
||||
_version = System.getProperty("jetty.version","Unknown");
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural language sorting for key names.
|
||||
*/
|
||||
private Comparator<String> keySorter = new Comparator<String>()
|
||||
{
|
||||
private 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 Map<String, Classpath> _classpaths = new HashMap<String, Classpath>();
|
||||
private List<String> _xml = new ArrayList<String>();
|
||||
private Set<String> _policies = new HashSet<String>();
|
||||
private String _classname = null;
|
||||
private Map<String, String> _properties = new HashMap<String, String>();
|
||||
private int argCount = 0;
|
||||
|
||||
private boolean addClasspathComponent(List<String> 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<String> 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<String> sections, File dir, boolean recurse) throws IOException
|
||||
{
|
||||
List<File> entries = new ArrayList<File>();
|
||||
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() && recurse)
|
||||
addJars(sections,entry,recurse);
|
||||
else
|
||||
{
|
||||
String name = entry.getName().toLowerCase();
|
||||
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 = getSystemProperty(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 combined classpath representing the default classpath plus all named sections.
|
||||
*
|
||||
* NOTE: the default classpath will be prepended, and the '*' classpath will be appended.
|
||||
*
|
||||
* @param sectionIds
|
||||
* 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<String> sectionIds)
|
||||
{
|
||||
Classpath cp = new Classpath();
|
||||
|
||||
cp.overlay(_classpaths.get(DEFAULT_SECTION));
|
||||
for (String sectionId : sectionIds)
|
||||
{
|
||||
cp.overlay(_classpaths.get(sectionId));
|
||||
}
|
||||
cp.overlay(_classpaths.get("*"));
|
||||
return cp;
|
||||
}
|
||||
|
||||
public String getMainClassname()
|
||||
{
|
||||
return _classname;
|
||||
}
|
||||
|
||||
public String getProperty(String name)
|
||||
{
|
||||
if ("version".equalsIgnoreCase(name))
|
||||
return _version;
|
||||
|
||||
return _properties.get(name);
|
||||
}
|
||||
|
||||
public String getProperty(String name, String dftValue)
|
||||
{
|
||||
if (_properties.containsKey(name))
|
||||
return _properties.get(name);
|
||||
return dftValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the classpath for the named section
|
||||
*
|
||||
* @param sectionId
|
||||
* @return
|
||||
*/
|
||||
public Classpath getSectionClasspath(String sectionId)
|
||||
{
|
||||
return _classpaths.get(sectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of section Ids.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<String> getSectionIds()
|
||||
{
|
||||
Set<String> ids = new TreeSet<String>(keySorter);
|
||||
ids.addAll(_classpaths.keySet());
|
||||
return ids;
|
||||
}
|
||||
|
||||
private String getSystemProperty(String name)
|
||||
{
|
||||
if ("version".equalsIgnoreCase(name))
|
||||
return _version;
|
||||
if (_properties.containsKey(name))
|
||||
return _properties.get(name);
|
||||
return System.getProperty(name);
|
||||
}
|
||||
|
||||
public List<String> getXmlConfigs()
|
||||
{
|
||||
return _xml;
|
||||
}
|
||||
|
||||
private boolean isAvailable(List<String> sections, String classname)
|
||||
{
|
||||
// Try default/parent class loader first.
|
||||
try
|
||||
{
|
||||
Class.forName(classname);
|
||||
return true;
|
||||
}
|
||||
catch (NoClassDefFoundError e)
|
||||
{
|
||||
debug(e);
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
debug(e);
|
||||
}
|
||||
|
||||
// Try section classloaders instead
|
||||
ClassLoader loader;
|
||||
Classpath classpath;
|
||||
for (String sectionId : sections)
|
||||
{
|
||||
classpath = _classpaths.get(sectionId);
|
||||
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(e);
|
||||
}
|
||||
}
|
||||
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 buf
|
||||
* @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<String> sections = new ArrayList<String>();
|
||||
sections.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("]"))
|
||||
{
|
||||
sections = Arrays.asList(trim.substring(1,trim.length() - 1).split(","));
|
||||
// Ensure section classpaths exist
|
||||
for (String sectionId : sections)
|
||||
{
|
||||
if (!_classpaths.containsKey(sectionId))
|
||||
{
|
||||
_classpaths.put(sectionId,new Classpath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(sections,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);
|
||||
}
|
||||
else
|
||||
// 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);
|
||||
}
|
||||
else
|
||||
// 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);
|
||||
}
|
||||
else
|
||||
// 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(sections,dir,false);
|
||||
}
|
||||
else
|
||||
// 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(sections,dir,true);
|
||||
}
|
||||
else
|
||||
// Add raw classpath directory to classpath
|
||||
if (subject.endsWith("/"))
|
||||
{
|
||||
// class directory
|
||||
File cd = new File(fixPath(file));
|
||||
String d = cd.getCanonicalPath();
|
||||
boolean added = addClasspathComponent(sections,d);
|
||||
debug((added?" CLASSPATH+=":" !") + d);
|
||||
}
|
||||
else
|
||||
// Add XML configuration
|
||||
if (subject.toLowerCase().endsWith(".xml"))
|
||||
{
|
||||
// Config file
|
||||
File f = new File(fixPath(file));
|
||||
if (f.exists())
|
||||
_xml.add(f.getCanonicalPath());
|
||||
debug(" ARGS+=" + f);
|
||||
}
|
||||
else
|
||||
// Set the main class to execute (overrides any previously set)
|
||||
if (subject.toLowerCase().endsWith(".class"))
|
||||
{
|
||||
// Class
|
||||
String cn = expand(subject.substring(0,subject.length() - 6));
|
||||
if (cn != null && cn.length() > 0)
|
||||
{
|
||||
debug(" CLASS=" + cn);
|
||||
_classname = cn;
|
||||
}
|
||||
}
|
||||
else
|
||||
// Add raw classpath entry
|
||||
if (subject.toLowerCase().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(sections,cn);
|
||||
}
|
||||
}
|
||||
else
|
||||
// Add Security Policy file reference
|
||||
if (subject.toLowerCase().endsWith(".policy"))
|
||||
{
|
||||
//policy file to parse
|
||||
String cn = expand(subject.substring(0,subject.length()));
|
||||
if (cn != null && cn.length() > 0)
|
||||
{
|
||||
debug(" POLICY=" + cn);
|
||||
_policies.add(fixPath(cn));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// single JAR file
|
||||
File f = new File(fixPath(file));
|
||||
if (f.exists())
|
||||
{
|
||||
String d = f.getCanonicalPath();
|
||||
boolean added = addClasspathComponent(sections,d);
|
||||
if (!added)
|
||||
{
|
||||
added = addClasspathPath(sections,expand(subject));
|
||||
}
|
||||
debug((added?" CLASSPATH+=":" !") + d);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.err.println("on line: '" + line + "'");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
close(buf);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
_properties.put(name,value);
|
||||
}
|
||||
|
||||
public Policy getPolicyInstance(ClassLoader cl) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
|
||||
InstantiationException, IllegalAccessException, InvocationTargetException
|
||||
{
|
||||
Class<?> jettyPolicy = cl.loadClass("org.eclipse.jetty.policy.JettyPolicy");
|
||||
Constructor<?> c = jettyPolicy.getConstructor(new Class[]
|
||||
{ Set.class, Map.class });
|
||||
Object policyClass = c.newInstance(_policies,_properties);
|
||||
|
||||
if (policyClass instanceof Policy)
|
||||
{
|
||||
return (Policy)policyClass;
|
||||
}
|
||||
|
||||
throw new ClassCastException("Unable to cast to " + Policy.class.getName() + " : " + policyClass.getClass().getName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) Webtide LLC
|
||||
// ------------------------------------------------------------------------
|
||||
// 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.apache.org/licenses/LICENSE-2.0.txt
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
package org.eclipse.jetty.start;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.CollationKey;
|
||||
import java.text.Collator;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Smart comparator for filenames, with natural language sorting, and files sorted before sub directories.
|
||||
*/
|
||||
public class FilenameComparator implements Comparator<File>
|
||||
{
|
||||
public static final FilenameComparator INSTANCE = new FilenameComparator();
|
||||
private Collator collator = Collator.getInstance();
|
||||
|
||||
public int compare(File o1, File o2)
|
||||
{
|
||||
if (o1.isFile())
|
||||
{
|
||||
if (o2.isFile())
|
||||
{
|
||||
CollationKey key1 = toKey(o1);
|
||||
CollationKey key2 = toKey(o2);
|
||||
return key1.compareTo(key2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Push o2 directories below o1 files
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (o2.isDirectory())
|
||||
{
|
||||
CollationKey key1 = toKey(o1);
|
||||
CollationKey key2 = toKey(o2);
|
||||
return key1.compareTo(key2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Push o2 files above o1 directories
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CollationKey toKey(File f)
|
||||
{
|
||||
return collator.getCollationKey(f.getAbsolutePath());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) Webtide LLC
|
||||
// ------------------------------------------------------------------------
|
||||
// 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.apache.org/licenses/LICENSE-2.0.txt
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
|
||||
package org.eclipse.jetty.start;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Attempt to determine the version of the Jar File based on common version locations.
|
||||
*/
|
||||
public class JarVersion
|
||||
{
|
||||
private static JarEntry findEntry(JarFile jar, String regex)
|
||||
{
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher;
|
||||
Enumeration<JarEntry> en = jar.entries();
|
||||
while (en.hasMoreElements())
|
||||
{
|
||||
JarEntry entry = en.nextElement();
|
||||
matcher = pattern.matcher(entry.getName());
|
||||
if (matcher.matches())
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getBundleVersion(Manifest manifest)
|
||||
{
|
||||
Attributes attribs = manifest.getMainAttributes();
|
||||
if (attribs == null)
|
||||
return null;
|
||||
|
||||
String version = attribs.getValue("Bundle-Version");
|
||||
if (version == null)
|
||||
return null;
|
||||
|
||||
return stripV(version);
|
||||
}
|
||||
|
||||
private static String getMainManifestImplVersion(Manifest manifest)
|
||||
{
|
||||
Attributes attribs = manifest.getMainAttributes();
|
||||
if (attribs == null)
|
||||
return null;
|
||||
|
||||
String version = attribs.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
|
||||
if (version == null)
|
||||
return null;
|
||||
|
||||
return stripV(version);
|
||||
}
|
||||
|
||||
private static String getMavenVersion(JarFile jar) throws IOException
|
||||
{
|
||||
JarEntry pomProp = findEntry(jar,"META-INF/maven/.*/pom\\.properties$");
|
||||
if (pomProp == null)
|
||||
return null;
|
||||
|
||||
InputStream stream = null;
|
||||
|
||||
try
|
||||
{
|
||||
stream = jar.getInputStream(pomProp);
|
||||
Properties props = new Properties();
|
||||
props.load(stream);
|
||||
|
||||
String version = props.getProperty("version");
|
||||
if (version == null)
|
||||
return null;
|
||||
|
||||
return stripV(version);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Main.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSubManifestImplVersion(Manifest manifest)
|
||||
{
|
||||
Map<String, Attributes> entries = manifest.getEntries();
|
||||
|
||||
for (Attributes attribs : entries.values())
|
||||
{
|
||||
if (attribs == null)
|
||||
continue; // skip entry
|
||||
|
||||
String version = attribs.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
|
||||
if (version == null)
|
||||
continue; // empty, no value, skip it
|
||||
|
||||
return stripV(version);
|
||||
}
|
||||
|
||||
return null; // no valid impl version entries found
|
||||
}
|
||||
|
||||
public static String getVersion(File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
JarFile jar = new JarFile(file);
|
||||
|
||||
String version = null;
|
||||
|
||||
Manifest manifest = jar.getManifest();
|
||||
|
||||
version = getMainManifestImplVersion(manifest);
|
||||
if (version != null)
|
||||
return version;
|
||||
|
||||
version = getSubManifestImplVersion(manifest);
|
||||
if (version != null)
|
||||
return version;
|
||||
|
||||
version = getBundleVersion(manifest);
|
||||
if (version != null)
|
||||
return version;
|
||||
|
||||
version = getMavenVersion(jar);
|
||||
if (version != null)
|
||||
return version;
|
||||
|
||||
return "(not specified)";
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return "(error: " + e.getClass().getSimpleName() + " " + e.getMessage() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static String stripV(String version)
|
||||
{
|
||||
if (version.charAt(0) == 'v')
|
||||
return version.substring(1);
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -63,10 +63,8 @@ public class Monitor extends Thread
|
|||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
if (Main.DEBUG)
|
||||
e.printStackTrace();
|
||||
else
|
||||
System.err.println(e.toString());
|
||||
Config.debug(e);
|
||||
System.err.println(e.toString());
|
||||
}
|
||||
if (_socket!=null)
|
||||
this.start();
|
||||
|
@ -74,6 +72,7 @@ public class Monitor extends Thread
|
|||
System.err.println("WARN: Not listening on monitor port: "+_port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while (true)
|
||||
|
@ -89,7 +88,7 @@ public class Monitor extends Thread
|
|||
continue;
|
||||
|
||||
String cmd=lin.readLine();
|
||||
if (Main.DEBUG) System.err.println("command="+cmd);
|
||||
Config.debug("command=" + cmd);
|
||||
if ("stop".equals(cmd))
|
||||
{
|
||||
try {socket.close();}catch(Exception e){e.printStackTrace();}
|
||||
|
@ -104,10 +103,8 @@ public class Monitor extends Thread
|
|||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
if (Main.DEBUG)
|
||||
e.printStackTrace();
|
||||
else
|
||||
System.err.println(e.toString());
|
||||
Config.debug(e);
|
||||
System.err.println(e.toString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
Usage: java -jar start.jar [options] [properties] [configs]
|
||||
Run Jetty Server Standalone.
|
||||
|
||||
Common Options:
|
||||
--help This help / usage information.
|
||||
--version Print the version information for Jetty, then exit.
|
||||
--stop Stop the running Jetty instance.
|
||||
|
||||
Advanced Options:
|
||||
--secure Enable Security:
|
||||
* JVM Security Manager
|
||||
* Security Policies
|
||||
* Secure Logging
|
||||
* Audit Logging
|
||||
--exec-print Print the command line that the start.jar uses to start
|
||||
jetty, then exit.
|
||||
(useful in shell scripts)
|
||||
--list-modes List available classpath mode options, then exit.
|
||||
(see OPTION property in section below)
|
||||
|
||||
Properties:
|
||||
Execution properties, similar in scope to JVM Properties.
|
||||
NOTE: Not all properties are listed here.
|
||||
org.eclipse.jetty.util.log.class=[class]
|
||||
A Low Level Jetty Logger Implementation to use
|
||||
(default: org.eclipse.jetty.util.log.Slf4jLog)
|
||||
org.eclipse.jetty.util.log.DEBUG=[boolean]
|
||||
Debug logs will be produced, along with the default logging levels of
|
||||
INFO, WARN, and ERROR
|
||||
(default: false)
|
||||
org.eclipse.jetty.util.log.VERBOSE=[boolean]
|
||||
Verbose logging is produced, including ignored exceptions
|
||||
(default: false)
|
||||
org.eclipse.jetty.util.log.IGNORED=[boolean]
|
||||
Ignored exceptions are logged, independent of DEBUG and VERBOSE settings
|
||||
(default: false)
|
||||
STOP.PORT=[number]
|
||||
The port to use to stop the running Jetty server.
|
||||
Required along with STOP.KEY if you want to use the --stop option above.
|
||||
STOP.KEY=[alphanumeric]
|
||||
The passphrase defined to stop the server.
|
||||
Requried along with STOP.PORT if you want to use the --stop option above.
|
||||
OPTIONS=[mode,mode,...]
|
||||
Classpath Options to use. Eg: All, Server, jmx, webapp, plus, etc...
|
||||
(default: "default,*")
|
||||
@OPTIONS@
|
||||
|
||||
Configs:
|
||||
XML Configurations to use.
|
||||
@CONFIGS@
|
|
@ -0,0 +1,562 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) Webtide LLC
|
||||
// ------------------------------------------------------------------------
|
||||
// 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.apache.org/licenses/LICENSE-2.0.txt
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
|
||||
package org.eclipse.jetty.start;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class ConfigTest extends TestCase
|
||||
{
|
||||
private File jettyHomeDir;
|
||||
private File resourcesDir;
|
||||
|
||||
private void assertEquals(String msg, Classpath expected, Classpath actual)
|
||||
{
|
||||
assertNotNull(msg + " : expected classpath should not be null",expected);
|
||||
assertNotNull(msg + " : actual classpath should not be null",actual);
|
||||
assertTrue(msg + " : expected should have an entry",expected.count() >= 1);
|
||||
assertTrue(msg + " : actual should have an entry",actual.count() >= 1);
|
||||
if (expected.count() != actual.count())
|
||||
{
|
||||
expected.dump(System.err);
|
||||
actual.dump(System.err);
|
||||
assertEquals(msg + " : count",expected.count(),actual.count());
|
||||
}
|
||||
|
||||
List<File> actualEntries = Arrays.asList(actual.getElements());
|
||||
List<File> expectedEntries = Arrays.asList(expected.getElements());
|
||||
|
||||
int len = expectedEntries.size();
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
File expectedFile = expectedEntries.get(i);
|
||||
File actualFile = actualEntries.get(i);
|
||||
if (!expectedFile.equals(actualFile))
|
||||
{
|
||||
expected.dump(System.err);
|
||||
actual.dump(System.err);
|
||||
assertEquals(msg + ": entry [" + i + "]",expectedEntries.get(i),actualEntries.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEquals(String msg, Collection<String> expected, Collection<String> actual)
|
||||
{
|
||||
assertTrue(msg + " : expected should have an entry",expected.size() >= 1);
|
||||
assertEquals(msg + " : size",expected.size(),actual.size());
|
||||
for (String expectedVal : expected)
|
||||
{
|
||||
assertTrue(msg + " : should contain <" + expectedVal + ">",actual.contains(expectedVal));
|
||||
}
|
||||
}
|
||||
|
||||
private String getJettyEtcFile(String name)
|
||||
{
|
||||
File etc = new File(getTestableJettyHome(),"etc");
|
||||
return new File(etc,name).getAbsolutePath();
|
||||
}
|
||||
|
||||
private File getJettyHomeDir()
|
||||
{
|
||||
if (jettyHomeDir == null)
|
||||
{
|
||||
jettyHomeDir = new File(getTestResourcesDir(),"jetty.home");
|
||||
}
|
||||
|
||||
return jettyHomeDir;
|
||||
}
|
||||
|
||||
private String getTestableJettyHome()
|
||||
{
|
||||
return getJettyHomeDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
private File getTestResourcesDir()
|
||||
{
|
||||
if (resourcesDir == null)
|
||||
{
|
||||
File src = new File(System.getProperty("user.dir"),"src");
|
||||
File test = new File(src,"test");
|
||||
resourcesDir = new File(test,"resources");
|
||||
}
|
||||
|
||||
return resourcesDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT "/=" for assign canonical path
|
||||
*/
|
||||
public void testSubjectAssignCanonicalPath() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("test.resources.dir/=src/test/resources\n");
|
||||
|
||||
Config cfg = new Config();
|
||||
cfg.parse(buf);
|
||||
|
||||
assertEquals(getTestResourcesDir().getCanonicalPath(),cfg.getProperty("test.resources.dir"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT "~=" for assigning Start Properties
|
||||
*/
|
||||
public void testSubjectAssignStartProperty() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("test.jetty.start.text~=foo\n");
|
||||
buf.append("test.jetty.start.quote~=Eatagramovabits\n");
|
||||
|
||||
Config options = new Config();
|
||||
options.parse(buf);
|
||||
|
||||
assertEquals("foo",options.getProperty("test.jetty.start.text"));
|
||||
assertEquals("Eatagramovabits",options.getProperty("test.jetty.start.quote"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT "=" for assigning System Properties
|
||||
*/
|
||||
public void testSubjectAssignSystemProperty() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("test.jetty.start.text=foo\n");
|
||||
buf.append("test.jetty.start.quote=Eatagramovabits\n");
|
||||
|
||||
Config options = new Config();
|
||||
options.parse(buf);
|
||||
|
||||
assertEquals("foo",System.getProperty("test.jetty.start.text"));
|
||||
assertEquals("Eatagramovabits",System.getProperty("test.jetty.start.quote"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with "/**", all jar and zip components in dir (deep, recursive)
|
||||
*/
|
||||
public void testSubjectComponentDirDeep() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("$(jetty.home)/lib/**\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath actual = options.getClasspath();
|
||||
Classpath expected = new Classpath();
|
||||
|
||||
File lib = new File(getJettyHomeDir(),"lib");
|
||||
|
||||
expected.addComponent(new File(lib,"core.jar"));
|
||||
expected.addComponent(new File(lib,"example.jar"));
|
||||
expected.addComponent(new File(lib,"http.jar"));
|
||||
expected.addComponent(new File(lib,"io.jar"));
|
||||
expected.addComponent(new File(lib,"JSR.ZIP"));
|
||||
expected.addComponent(new File(lib,"LOGGING.JAR"));
|
||||
expected.addComponent(new File(lib,"server.jar"));
|
||||
expected.addComponent(new File(lib,"spec.zip"));
|
||||
expected.addComponent(new File(lib,"util.jar"));
|
||||
expected.addComponent(new File(lib,"xml.jar"));
|
||||
|
||||
File ext = new File(lib,"ext");
|
||||
expected.addComponent(new File(ext,"custom-impl.jar"));
|
||||
|
||||
assertEquals("Components (Deep)",expected,actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with "/*", all jar and zip components in dir (shallow, no recursion)
|
||||
*/
|
||||
public void testSubjectComponentDirShallow() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("# Example of any shallow components in /lib/\n");
|
||||
buf.append("$(jetty.home)/lib/*\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath actual = options.getClasspath();
|
||||
Classpath expected = new Classpath();
|
||||
|
||||
File lib = new File(getJettyHomeDir(),"lib");
|
||||
|
||||
expected.addComponent(new File(lib,"core.jar"));
|
||||
expected.addComponent(new File(lib,"example.jar"));
|
||||
expected.addComponent(new File(lib,"http.jar"));
|
||||
expected.addComponent(new File(lib,"io.jar"));
|
||||
expected.addComponent(new File(lib,"JSR.ZIP"));
|
||||
expected.addComponent(new File(lib,"LOGGING.JAR"));
|
||||
expected.addComponent(new File(lib,"server.jar"));
|
||||
expected.addComponent(new File(lib,"spec.zip"));
|
||||
expected.addComponent(new File(lib,"util.jar"));
|
||||
expected.addComponent(new File(lib,"xml.jar"));
|
||||
|
||||
assertEquals("Components (Shallow)",expected,actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with ".class", a Main Class
|
||||
*/
|
||||
public void testSubjectMainClass() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("org.eclipse.jetty.xml.XmlConfiguration.class");
|
||||
|
||||
Config options = new Config();
|
||||
options.parse(buf);
|
||||
|
||||
assertEquals("org.eclipse.jetty.xml.XmlConfiguration",options.getMainClassname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with ".class", a Main Class
|
||||
*/
|
||||
public void testSubjectMainClassConditionalPropertySet() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("org.eclipse.jetty.xml.XmlConfiguration.class\n");
|
||||
buf.append("${start.class}.class property start.class");
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("start.class","net.company.server.Start");
|
||||
options.parse(buf);
|
||||
|
||||
assertEquals("net.company.server.Start",options.getMainClassname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with ".class", a Main Class
|
||||
*/
|
||||
public void testSubjectMainClassConditionalPropertyUnset() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("org.eclipse.jetty.xml.XmlConfiguration.class\n");
|
||||
buf.append("${start.class}.class property start.class");
|
||||
|
||||
Config options = new Config();
|
||||
// The "start.class" property is unset.
|
||||
options.parse(buf);
|
||||
|
||||
assertEquals("org.eclipse.jetty.xml.XmlConfiguration",options.getMainClassname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with "/", a simple Classpath Entry
|
||||
*/
|
||||
public void testSubjectSimpleComponent() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("$(jetty.home)/resources/\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath actual = options.getClasspath();
|
||||
Classpath expected = new Classpath();
|
||||
|
||||
expected.addComponent(new File(getJettyHomeDir(),"resources"));
|
||||
|
||||
assertEquals("Simple Component",expected,actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with "/", a simple Classpath Entry
|
||||
*/
|
||||
public void testSubjectSimpleComponentMultiple() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("$(jetty.home)/resources/\n");
|
||||
buf.append("$(jetty.home)/etc/\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath actual = options.getClasspath();
|
||||
Classpath expected = new Classpath();
|
||||
|
||||
expected.addComponent(new File(getJettyHomeDir(),"resources"));
|
||||
expected.addComponent(new File(getJettyHomeDir(),"etc"));
|
||||
|
||||
assertEquals("Simple Component",expected,actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with "/", a simple Classpath Entry
|
||||
*/
|
||||
public void testSubjectSimpleComponentNotExists() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("$(jetty.home)/resources/\n");
|
||||
buf.append("$(jetty.home)/foo/\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath actual = options.getClasspath();
|
||||
Classpath expected = new Classpath();
|
||||
|
||||
expected.addComponent(new File(getJettyHomeDir(),"resources"));
|
||||
|
||||
assertEquals("Simple Component",expected,actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with ".xml", an XML Configuration File
|
||||
*/
|
||||
public void testSubjectXmlConfigAlt() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
// Doesn't exist
|
||||
buf.append("$(jetty.home)/etc/jetty.xml nargs == 0\n");
|
||||
// test-alt does exist.
|
||||
buf.append("./src/test/resources/test-alt.xml nargs == 0 AND ! exists $(jetty.home)/etc/jetty.xml");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
List<String> actual = options.getXmlConfigs();
|
||||
String expected = new File("src/test/resources/test-alt.xml").getAbsolutePath();
|
||||
assertEquals("XmlConfig.size",1,actual.size());
|
||||
assertEquals(expected,actual.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with ".xml", an XML Configuration File
|
||||
*/
|
||||
public void testSubjectXmlConfigDefault() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("$(jetty.home)/etc/test-jetty.xml nargs == 0\n");
|
||||
buf.append("./jetty-server/src/main/config/etc/test-jetty.xml nargs == 0 AND ! exists $(jetty.home)/etc/test-jetty.xml");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
List<String> actual = options.getXmlConfigs();
|
||||
String expected = getJettyEtcFile("test-jetty.xml");
|
||||
assertEquals("XmlConfig.size",1,actual.size());
|
||||
assertEquals(expected,actual.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for SUBJECT ending with ".xml", an XML Configuration File.
|
||||
*/
|
||||
public void testSubjectXmlConfigMultiple() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("$(jetty.home)/etc/test-jetty.xml nargs == 0\n");
|
||||
buf.append("$(jetty.home)/etc/test-jetty-ssl.xml nargs == 0\n");
|
||||
buf.append("$(jetty.home)/etc/test-jetty-security.xml nargs == 0\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
List<String> actual = options.getXmlConfigs();
|
||||
List<String> expected = new ArrayList<String>();
|
||||
expected.add(getJettyEtcFile("test-jetty.xml"));
|
||||
expected.add(getJettyEtcFile("test-jetty-ssl.xml"));
|
||||
expected.add(getJettyEtcFile("test-jetty-security.xml"));
|
||||
|
||||
assertEquals("Multiple XML Configs",expected,actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Section Handling
|
||||
*/
|
||||
public void testSectionClasspathSingle() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[All]\n");
|
||||
buf.append("$(jetty.home)/lib/core-test.jar\n");
|
||||
buf.append("$(jetty.home)/lib/util.jar\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath defaultClasspath = options.getClasspath();
|
||||
assertNotNull("Default Classpath should not be null",defaultClasspath);
|
||||
Classpath foocp = options.getSectionClasspath("Foo");
|
||||
assertNull("Foo Classpath should not exist",foocp);
|
||||
|
||||
Classpath allcp = options.getSectionClasspath("All");
|
||||
assertNotNull("Classpath section 'All' should exist",allcp);
|
||||
|
||||
File lib = new File(getJettyHomeDir(),"lib");
|
||||
|
||||
Classpath expected = new Classpath();
|
||||
expected.addComponent(new File(lib,"core-test.jar"));
|
||||
expected.addComponent(new File(lib,"util.jar"));
|
||||
|
||||
assertEquals("Single Classpath Section",expected,allcp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Section Handling
|
||||
*/
|
||||
public void testSectionClasspathAvailable() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[All]\n");
|
||||
buf.append("$(jetty.home)/lib/core.jar ! available org.eclipse.jetty.dummy.Handler\n");
|
||||
buf.append("$(jetty.home)/lib/util.jar ! available org.eclipse.jetty.dummy.StringUtils\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config options = new Config();
|
||||
options.setProperty("jetty.home",jettyHome);
|
||||
options.parse(buf);
|
||||
|
||||
Classpath defaultClasspath = options.getClasspath();
|
||||
assertNotNull("Default Classpath should not be null",defaultClasspath);
|
||||
Classpath foocp = options.getSectionClasspath("Foo");
|
||||
assertNull("Foo Classpath should not exist",foocp);
|
||||
|
||||
Classpath allcp = options.getSectionClasspath("All");
|
||||
assertNotNull("Classpath section 'All' should exist",allcp);
|
||||
|
||||
File lib = new File(getJettyHomeDir(),"lib");
|
||||
|
||||
Classpath expected = new Classpath();
|
||||
expected.addComponent(new File(lib,"core.jar"));
|
||||
expected.addComponent(new File(lib,"util.jar"));
|
||||
|
||||
assertEquals("Single Classpath Section",expected,allcp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Section Handling, with multiple defined sections.
|
||||
*/
|
||||
public void testSectionClasspathMultiples() throws IOException
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("# default\n");
|
||||
buf.append("$(jetty.home)/lib/spec.zip\n");
|
||||
buf.append("\n");
|
||||
buf.append("[*]\n");
|
||||
buf.append("$(jetty.home)/lib/io.jar\n");
|
||||
buf.append("$(jetty.home)/lib/util.jar\n");
|
||||
buf.append("\n");
|
||||
buf.append("[All,server,default]\n");
|
||||
buf.append("$(jetty.home)/lib/core.jar\n");
|
||||
buf.append("$(jetty.home)/lib/server.jar\n");
|
||||
buf.append("$(jetty.home)/lib/http.jar\n");
|
||||
buf.append("\n");
|
||||
buf.append("[All,xml,default]\n");
|
||||
buf.append("$(jetty.home)/lib/xml.jar\n");
|
||||
buf.append("\n");
|
||||
buf.append("[All,logging]\n");
|
||||
buf.append("$(jetty.home)/lib/LOGGING.JAR\n");
|
||||
|
||||
String jettyHome = getTestableJettyHome();
|
||||
|
||||
Config cfg = new Config();
|
||||
cfg.setProperty("jetty.home",jettyHome);
|
||||
cfg.parse(buf);
|
||||
|
||||
Classpath defaultClasspath = cfg.getClasspath();
|
||||
assertNotNull("Default Classpath should not be null",defaultClasspath);
|
||||
|
||||
Classpath foocp = cfg.getSectionClasspath("Foo");
|
||||
assertNull("Foo Classpath should not exist",foocp);
|
||||
|
||||
// Test if entire section list can be fetched
|
||||
Set<String> sections = cfg.getSectionIds();
|
||||
|
||||
Set<String> expected = new HashSet<String>();
|
||||
expected.add(Config.DEFAULT_SECTION);
|
||||
expected.add("*");
|
||||
expected.add("All");
|
||||
expected.add("server");
|
||||
expected.add("default");
|
||||
expected.add("xml");
|
||||
expected.add("logging");
|
||||
|
||||
assertEquals("Multiple Section IDs",expected,sections);
|
||||
|
||||
// Test fetch of specific section by name works
|
||||
Classpath cpAll = cfg.getSectionClasspath("All");
|
||||
assertNotNull("Classpath section 'All' should exist",cpAll);
|
||||
|
||||
File lib = new File(getJettyHomeDir(),"lib");
|
||||
|
||||
Classpath expectedAll = new Classpath();
|
||||
expectedAll.addComponent(new File(lib,"core.jar"));
|
||||
expectedAll.addComponent(new File(lib,"server.jar"));
|
||||
expectedAll.addComponent(new File(lib,"http.jar"));
|
||||
expectedAll.addComponent(new File(lib,"xml.jar"));
|
||||
expectedAll.addComponent(new File(lib,"LOGGING.JAR"));
|
||||
|
||||
assertEquals("Classpath 'All' Section",expectedAll,cpAll);
|
||||
|
||||
// Test combined classpath fetch of multiple sections works
|
||||
List<String> activated = new ArrayList<String>();
|
||||
activated.add("server");
|
||||
activated.add("logging");
|
||||
|
||||
Classpath cpCombined = cfg.getCombinedClasspath(activated);
|
||||
|
||||
Classpath expectedCombined = new Classpath();
|
||||
// from default
|
||||
expectedCombined.addComponent(new File(lib,"spec.zip"));
|
||||
// from 'server'
|
||||
expectedCombined.addComponent(new File(lib,"core.jar"));
|
||||
expectedCombined.addComponent(new File(lib,"server.jar"));
|
||||
expectedCombined.addComponent(new File(lib,"http.jar"));
|
||||
// from 'logging'
|
||||
expectedCombined.addComponent(new File(lib,"LOGGING.JAR"));
|
||||
// from '*'
|
||||
expectedCombined.addComponent(new File(lib,"io.jar"));
|
||||
expectedCombined.addComponent(new File(lib,"util.jar"));
|
||||
|
||||
assertEquals("Classpath combined 'server,logging'",expectedCombined,cpCombined);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) Webtide LLC
|
||||
// ------------------------------------------------------------------------
|
||||
// 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.apache.org/licenses/LICENSE-2.0.txt
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
package org.eclipse.jetty.start;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class VersionTest extends TestCase
|
||||
{
|
||||
public void testDefaultVersion()
|
||||
{
|
||||
Version version = new Version();
|
||||
|
||||
assertEquals("Default version difference to 0.0.0",0,version.compare(new Version("0.0.0")));
|
||||
}
|
||||
|
||||
public void testNewerVersion() {
|
||||
assertIsNewer("0.0.0", "0.0.1");
|
||||
assertIsNewer("0.1.0", "0.1.1");
|
||||
assertIsNewer("1.5.0", "1.6.0");
|
||||
// assertIsNewer("1.6.0_12", "1.6.0_16"); // JDK version spec?
|
||||
}
|
||||
|
||||
public void testOlderVersion() {
|
||||
assertIsOlder("0.0.1", "0.0.0");
|
||||
assertIsOlder("0.1.1", "0.1.0");
|
||||
assertIsOlder("1.6.0", "1.5.0");
|
||||
}
|
||||
|
||||
private void assertIsOlder(String basever, String testver)
|
||||
{
|
||||
Version vbase = new Version(basever);
|
||||
Version vtest = new Version(testver);
|
||||
|
||||
assertTrue("Version [" + testver + "] should be older than [" + basever + "]",
|
||||
vtest.compare(vbase) == -1);
|
||||
}
|
||||
|
||||
private void assertIsNewer(String basever, String testver)
|
||||
{
|
||||
Version vbase = new Version(basever);
|
||||
Version vtest = new Version(testver);
|
||||
|
||||
assertTrue("Version [" + testver + "] should be newer than [" + basever + "]",
|
||||
vtest.compare(vbase) == 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
|
|
@ -0,0 +1 @@
|
|||
<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
|
|
@ -0,0 +1 @@
|
|||
<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
Many of these files are zero length on purpose.
|
||||
Of those jars that have content, a simple "Hello World" style class has been
|
||||
compiled (included) to allow the ConfigTest of available classes.
|
||||
This directory is used by the various tests, and having legitimate jar/zip files
|
||||
is often not important.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
# Nothing of importance, just used by ConfigTest.java
|
|
@ -0,0 +1 @@
|
|||
<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
|
Loading…
Reference in New Issue