Adding Module / Modules system classes for start.d

This commit is contained in:
Joakim Erdfelt 2013-08-21 15:11:56 -07:00
parent 77cbd9ec4b
commit 03cd15ea82
17 changed files with 815 additions and 0 deletions

View File

@ -0,0 +1,260 @@
//
// ========================================================================
// 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.FileReader;
import java.io.IOException;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Represents a Module metadata, as defined in Jetty.
*/
public class Module
{
public static class DepthComparator implements Comparator<Module>
{
private Collator collator = Collator.getInstance();
@Override
public int compare(Module o1, Module o2)
{
// order by depth first.
int diff = o1.depth - o2.depth;
if (diff != 0)
{
return diff;
}
// then by name (not really needed, but makes for predictable test cases)
CollationKey k1 = collator.getCollationKey(o1.name);
CollationKey k2 = collator.getCollationKey(o2.name);
return k1.compareTo(k2);
}
}
public static Module fromFile(File file) throws IOException
{
String name = file.getName();
// Strip .ini
name = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(name).replaceFirst("");
// XML Pattern
Pattern xmlPattern = Pattern.compile(".xml$",Pattern.CASE_INSENSITIVE);
Set<String> parents = new HashSet<>();
List<String> xmls = new ArrayList<>();
List<String> libs = new ArrayList<>();
try (FileReader reader = new FileReader(file))
{
try (BufferedReader buf = new BufferedReader(reader))
{
String line;
while ((line = buf.readLine()) != null)
{
line = line.trim();
if (line.length() <= 0)
{
continue; // skip empty lines
}
if (line.charAt(0) == '#')
{
continue; // skip lines with comments
}
// has assignment
int idx = line.indexOf('=');
if (idx >= 0)
{
String key = line.substring(0,idx);
String value = line.substring(idx + 1);
boolean handled = false;
switch (key.toUpperCase(Locale.ENGLISH))
{
case "DEPEND":
parents.add(value);
handled = true;
break;
case "LIB":
libs.add(value);
handled = true;
break;
}
if (handled)
{
continue; // no further processing of line needed
}
}
// Is it an XML line?
if (xmlPattern.matcher(line).find())
{
xmls.add(line);
continue; // legit xml
}
throw new IllegalArgumentException("Unrecognized Module Metadata line [" + line + "] in Module file [" + file + "]");
}
}
}
return new Module(name,parents,xmls,libs);
}
/** The name of this Module */
private final String name;
/** List of Modules, by name, that this Module depends on */
private final Set<String> parentNames;
/** The Edges to parent modules */
private final Set<Module> parentEdges;
/** The Edges to child modules */
private final Set<Module> childEdges;
/** The depth of the module in the tree */
private int depth = 0;
/** List of xml configurations for this Module */
private final List<String> xmls;
/** List of library options for this Module */
private final List<String> libs;
/** Is this Module enabled via start.jar command line, start.ini, or start.d/*.ini ? */
private boolean enabled = false;
public Module(String name, Set<String> parentNames, List<String> xmls, List<String> libs)
{
this.name = name;
this.parentNames = parentNames;
this.xmls = xmls;
this.libs = libs;
// initialize edge collections, will be filled out by Modules#buildGraph() later */
this.parentEdges = new HashSet<>();
this.childEdges = new HashSet<>();
}
public void addChildEdge(Module child)
{
this.childEdges.add(child);
}
public void addParentEdge(Module parent)
{
this.parentEdges.add(parent);
}
public Set<Module> getChildEdges()
{
return childEdges;
}
public int getDepth()
{
return depth;
}
public List<String> getLibs()
{
return libs;
}
public String getName()
{
return name;
}
public Set<Module> getParentEdges()
{
return parentEdges;
}
public Set<String> getParentNames()
{
return parentNames;
}
public List<String> getXmls()
{
return xmls;
}
public boolean isEnabled()
{
return enabled;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((name == null)?0:name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Module other = (Module)obj;
if (name == null)
{
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
return true;
}
public void setDepth(int depth)
{
this.depth = depth;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public String toString()
{
StringBuilder str = new StringBuilder();
str.append("Module[").append(name);
if (enabled)
{
str.append(",enabled");
}
str.append(']');
return str.toString();
}
}

View File

@ -0,0 +1,235 @@
//
// ========================================================================
// 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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Access for all modules declared, as well as what is enabled.
*/
public class Modules implements Iterable<Module>
{
private Map<String, Module> modules = new HashMap<>();
public void register(Module module)
{
modules.put(module.getName(),module);
}
private void bfsCalculateDepth(final Module module, final int depthNow)
{
int depth = depthNow + 1;
// Set depth on every child first
for (Module child : module.getChildEdges())
{
child.setDepth(Math.max(depth,child.getDepth()));
}
// Dive down
for (Module child : module.getChildEdges())
{
bfsCalculateDepth(child,depth);
}
}
/**
* Using the provided dependenies, build the module graph
*/
public void buildGraph()
{
// TODO: Validate / Enforce Directed Acyclic Graph
// Connect edges
for (Module module : modules.values())
{
for (String parentName : module.getParentNames())
{
Module parent = get(parentName);
if (parent != null)
{
module.addParentEdge(parent);
parent.addChildEdge(module);
}
}
}
// Calculate depth of all modules for sorting later
for (Module module : modules.values())
{
if (module.getParentEdges().isEmpty())
{
bfsCalculateDepth(module,0);
}
}
}
public Integer count()
{
return modules.size();
}
public void enable(String name)
{
Module module = modules.get(name);
if (module == null)
{
System.err.printf("WARNING: Cannot enable requested module [%s]: not a valid module name.%n",name);
return;
}
module.setEnabled(true);
}
private void findParents(Module module, Set<Module> active)
{
active.add(module);
for (Module parent : module.getParentEdges())
{
active.add(parent);
findParents(parent,active);
}
}
public void dump()
{
List<Module> ordered = new ArrayList<>();
ordered.addAll(modules.values());
Collections.sort(ordered,Collections.reverseOrder(new Module.DepthComparator()));
for (Module module : ordered)
{
System.out.printf("Module: %s%n",module.getName());
System.out.printf(" depth: %d%n",module.getDepth());
System.out.printf(" parents: [%s]%n",join(module.getParentNames(),','));
for (String xml : module.getXmls())
{
System.out.printf(" xml: %s%n",xml);
}
}
}
private String join(Collection<?> objs, char delim)
{
StringBuilder str = new StringBuilder();
boolean needDelim = false;
for (Object obj : objs)
{
if (needDelim)
{
str.append(delim);
}
str.append(obj);
needDelim = true;
}
return str.toString();
}
public Module get(String name)
{
Module module = modules.get(name);
if (module == null)
{
System.err.printf("WARNING: module not found [%s]%n",name);
}
return module;
}
@Override
public Iterator<Module> iterator()
{
return modules.values().iterator();
}
/**
* Resolve the execution order of the enabled modules, and all dependant modules, based on depth first transitive reduction.
*
* @return the list of active modules (plus dependant modules), in execution order.
*/
public List<Module> resolveEnabled()
{
Set<Module> active = new HashSet<Module>();
for (Module module : modules.values())
{
if (module.isEnabled())
{
findParents(module,active);
}
}
List<Module> ordered = new ArrayList<>();
ordered.addAll(active);
Collections.sort(ordered,new Module.DepthComparator());
return ordered;
}
// TODO: Resolve LIB names to actual java.io.File references via HomeBase
// TODO: Handle ${jetty.version} references here
// TODO: Handle *.jar filesystem glob style here
public List<String> normalizeLibs(List<Module> active)
{
List<String> libs = new ArrayList<>();
for (Module module : active)
{
for (String lib : module.getLibs())
{
if (!libs.contains(lib))
{
libs.add(lib);
}
}
}
return libs;
}
// TODO: Resolve XML names to actual java.io.File references via HomeBase
public List<String> normalizeXmls(List<Module> active)
{
List<String> xmls = new ArrayList<>();
for (Module module : active)
{
for (String xml : module.getXmls())
{
if (!xmls.contains(xml))
{
xmls.add(xml);
}
}
}
return xmls;
}
public void registerAll(BaseHome basehome) throws IOException
{
for (File file : basehome.listFiles("modules",new FS.FilenameRegexFilter("^.*\\.mod$")))
{
register(Module.fromFile(file));
}
}
}

View File

@ -0,0 +1,51 @@
//
// ========================================================================
// 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 static org.hamcrest.Matchers.*;
import java.io.File;
import java.io.IOException;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Assert;
import org.junit.Test;
public class ModuleTest
{
private Module loadTestHomeModule(String moduleFileName) throws IOException
{
File file = MavenTestingUtils.getTestResourceFile("usecases/home/modules/" + moduleFileName);
return Module.fromFile(file);
}
@Test
public void testLoadWebSocket() throws IOException
{
Module Module = loadTestHomeModule("websocket.mod");
Assert.assertThat("Module Name",Module.getName(),is("websocket"));
Assert.assertThat("Module Parents Size",Module.getParentNames().size(),is(2));
Assert.assertThat("Module Parents",Module.getParentNames(),containsInAnyOrder("annotations","server"));
Assert.assertThat("Module Xmls Size",Module.getXmls().size(),is(1));
Assert.assertThat("Module Xmls",Module.getXmls(),contains("etc/jetty-websocket.xml"));
Assert.assertThat("Module Options Size",Module.getLibs().size(),is(1));
Assert.assertThat("Module Options",Module.getLibs(),contains("lib/websockets/*.jar"));
}
}

View File

@ -0,0 +1,163 @@
//
// ========================================================================
// 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 static org.hamcrest.Matchers.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Assert;
import org.junit.Test;
public class ModulesTest
{
@Test
public void testLoadAllModules() throws IOException
{
File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
BaseHome basehome = new BaseHome(homeDir,homeDir);
Modules modules = new Modules();
modules.registerAll(basehome);
Assert.assertThat("Module count",modules.count(),is(11));
}
@Test
public void testResolve_ServerHttp() throws IOException
{
File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
BaseHome basehome = new BaseHome(homeDir,homeDir);
// Register modules
Modules modules = new Modules();
modules.registerAll(basehome);
modules.buildGraph();
// Enable 2 modules
modules.enable("server");
modules.enable("http");
// Collect active module list
List<Module> active = modules.resolveEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("base");
expectedNames.add("server");
expectedNames.add("http");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
Assert.assertThat("Resolved Names: " + actualNames,actualNames,contains(expectedNames.toArray()));
// Assert Library List
List<String> expectedLibs = new ArrayList<>();
expectedLibs.add("lib/jetty-util-${jetty.version}.jar");
expectedLibs.add("lib/jetty-io-${jetty.version}.jar");
expectedLibs.add("lib/servlet-api-3.1.jar");
expectedLibs.add("lib/jetty-schemas-3.1.jar");
expectedLibs.add("lib/jetty-http-${jetty.version}.jar");
expectedLibs.add("lib/jetty-continuation-${jetty.version}.jar");
expectedLibs.add("lib/jetty-server-${jetty.version}.jar");
List<String> actualLibs = modules.normalizeLibs(active);
Assert.assertThat("Resolved Libs: " + actualLibs,actualLibs,contains(expectedLibs.toArray()));
// Assert XML List
List<String> expectedXmls = new ArrayList<>();
expectedXmls.add("etc/jetty.xml");
expectedXmls.add("etc/jetty-http.xml");
List<String> actualXmls = modules.normalizeXmls(active);
Assert.assertThat("Resolved XMLs: " + actualXmls,actualXmls,contains(expectedXmls.toArray()));
}
@Test
public void testResolve_WebSocket() throws IOException
{
File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
BaseHome basehome = new BaseHome(homeDir,homeDir);
// Register modules
Modules modules = new Modules();
modules.registerAll(basehome);
modules.buildGraph();
// modules.dump();
// Enable 2 modules
modules.enable("websocket");
modules.enable("http");
// Collect active module list
List<Module> active = modules.resolveEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("base");
expectedNames.add("server");
expectedNames.add("http");
expectedNames.add("plus");
expectedNames.add("annotations");
expectedNames.add("websocket");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
Assert.assertThat("Resolved Names: " + actualNames,actualNames,contains(expectedNames.toArray()));
// Assert Library List
List<String> expectedLibs = new ArrayList<>();
expectedLibs.add("lib/jetty-util-${jetty.version}.jar");
expectedLibs.add("lib/jetty-io-${jetty.version}.jar");
expectedLibs.add("lib/servlet-api-3.1.jar");
expectedLibs.add("lib/jetty-schemas-3.1.jar");
expectedLibs.add("lib/jetty-http-${jetty.version}.jar");
expectedLibs.add("lib/jetty-continuation-${jetty.version}.jar");
expectedLibs.add("lib/jetty-server-${jetty.version}.jar");
expectedLibs.add("lib/jetty-plus-${jetty.version}.xml");
expectedLibs.add("lib/jetty-annotations-${jetty.version}.jar");
expectedLibs.add("lib/annotations/*.jar");
expectedLibs.add("lib/websockets/*.jar");
List<String> actualLibs = modules.normalizeLibs(active);
Assert.assertThat("Resolved Libs: " + actualLibs,actualLibs,contains(expectedLibs.toArray()));
// Assert XML List
List<String> expectedXmls = new ArrayList<>();
expectedXmls.add("etc/jetty.xml");
expectedXmls.add("etc/jetty-http.xml");
expectedXmls.add("etc/jetty-plus.xml");
expectedXmls.add("etc/jetty-annotations.xml");
expectedXmls.add("etc/jetty-websocket.xml");
List<String> actualXmls = modules.normalizeXmls(active);
Assert.assertThat("Resolved XMLs: " + actualXmls,actualXmls,contains(expectedXmls.toArray()));
}
}

View File

@ -0,0 +1,4 @@
MODULES=server,http
jetty.port=9090

View File

@ -0,0 +1,14 @@
#
# Jetty Annotation Scanning Module
#
# Annotations needs plus, and jndi features
DEPEND=plus
# Annotations needs jetty annotation jars
LIB=lib/jetty-annotations-${jetty.version}.jar
# Need annotation processing jars too
LIB=lib/annotations/*.jar
# Enable annotation scanning webapp configurations
etc/jetty-annotations.xml

View File

@ -0,0 +1,6 @@
#
# Base Module
#
LIB=lib/jetty-util-${jetty.version}.jar
LIB=lib/jetty-io-${jetty.version}.jar

View File

@ -0,0 +1,4 @@
DEPEND=server
etc/jetty-http.xml

View File

@ -0,0 +1,5 @@
DEPEND=server
etc/jetty-ssl.xml
etc/jetty-https.xml

View File

@ -0,0 +1,9 @@
#
# JMX Feature
#
# JMX jars (as defined in start.config)
LIB=lib/jetty-jmx-${jetty.version}.jar
# JMX configuration
etc/jetty-jmx.xml

View File

@ -0,0 +1,12 @@
#
# JNDI Support
#
DEPEND=server
DEPEND=plus
LIB=lib/jetty-jndi-${jetty.version}.jar
LIB=lib/jndi/*.jar
# Annotations needs annotations configuration
etc/jetty-server.xml

View File

@ -0,0 +1,9 @@
#
# Jetty Plus Module
#
DEPEND=server
LIB=lib/jetty-plus-${jetty.version}.xml
etc/jetty-plus.xml

View File

@ -0,0 +1,14 @@
#
# Base server
#
DEPEND=base
LIB=lib/servlet-api-3.1.jar
LIB=lib/jetty-schemas-3.1.jar
LIB=lib/jetty-http-${jetty.version}.jar
LIB=lib/jetty-continuation-${jetty.version}.jar
LIB=lib/jetty-server-${jetty.version}.jar
# Annotations needs annotations configuration
etc/jetty.xml

View File

@ -0,0 +1,5 @@
DEPEND=server
etc/jetty-ssl.xml
etc/jetty-spdy.xml

View File

@ -0,0 +1,14 @@
#
# WebSocket Feature
#
# WebSocket needs Annotations feature
DEPEND=server
DEPEND=annotations
# WebSocket needs websocket jars (as defined in start.config)
LIB=lib/websockets/*.jar
# WebSocket needs websocket configuration
etc/jetty-websocket.xml

View File

@ -0,0 +1,8 @@
#
# Jetty XML Configuration
#
DEPEND=base
LIB=lib/jetty-xml-${jetty.version}.jar

View File

@ -0,0 +1,2 @@
MODULES=server,http,jmx,annotations,websocket