Merge branch 'jetty-7' into release-7

This commit is contained in:
Thomas Becker 2013-09-10 15:33:19 +02:00
commit 262019a5e1
29 changed files with 1685 additions and 353 deletions

View File

@ -59,6 +59,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jndi</artifactId> <artifactId>jetty-jndi</artifactId>

View File

@ -526,7 +526,7 @@ public class AnnotationParser
public void parse (Resource dir, ClassNameResolver resolver) public void parse (Resource dir, ClassNameResolver resolver)
throws Exception throws Exception
{ {
if (!dir.isDirectory() || !dir.exists()) if (!dir.isDirectory() || !dir.exists() || dir.getName().startsWith("."))
return; return;
@ -538,15 +538,20 @@ public class AnnotationParser
Resource res = dir.addPath(files[f]); Resource res = dir.addPath(files[f]);
if (res.isDirectory()) if (res.isDirectory())
parse(res, resolver); parse(res, resolver);
String name = res.getName(); else
if (name.endsWith(".class"))
{ {
if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name)))) String fullname = res.getName();
{ String filename = res.getFile().getName();
Resource r = Resource.newResource(res.getURL());
scanClass(r.getInputStream());
}
if (isValidClassFileName(filename))
{
if ((resolver == null)|| (!resolver.isExcluded(fullname) && (!isParsed(fullname) || resolver.shouldOverride(fullname))))
{
Resource r = Resource.newResource(res.getURL());
scanClass(r.getInputStream());
}
}
} }
} }
catch (Exception ex) catch (Exception ex)
@ -581,8 +586,12 @@ public class AnnotationParser
{ {
try try
{ {
//skip directories
if (entry.isDirectory())
return;
String name = entry.getName(); String name = entry.getName();
if (name.toLowerCase(Locale.ENGLISH).endsWith(".class")) if (isValidClassFilePath(name) && isValidClassFileName(name))
{ {
String shortName = name.replace('/', '.').substring(0,name.length()-6); String shortName = name.replace('/', '.').substring(0,name.length()-6);
if ((resolver == null) if ((resolver == null)
@ -625,8 +634,12 @@ public class AnnotationParser
{ {
try try
{ {
//skip directories
if (entry.isDirectory())
return;
String name = entry.getName(); String name = entry.getName();
if (name.toLowerCase(Locale.ENGLISH).endsWith(".class")) if (isValidClassFilePath(name) && isValidClassFileName(name))
{ {
String shortName = name.replace('/', '.').substring(0,name.length()-6); String shortName = name.replace('/', '.').substring(0,name.length()-6);
@ -650,6 +663,14 @@ public class AnnotationParser
scanner.scan(null, uris, true); scanner.scan(null, uris, true);
} }
/**
* Parse a single jar file for classes.
*
* @param uri
* @param resolver
* @throws Exception
*/
public void parse (URI uri, final ClassNameResolver resolver) public void parse (URI uri, final ClassNameResolver resolver)
throws Exception throws Exception
{ {
@ -659,10 +680,79 @@ public class AnnotationParser
parse(uris, resolver); parse(uris, resolver);
} }
/**
* Scan a class for annotations.
*
* @param is
* @throws IOException
*/
private void scanClass (InputStream is) private void scanClass (InputStream is)
throws IOException throws IOException
{ {
ClassReader reader = new ClassReader(is); ClassReader reader = new ClassReader(is);
reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES); reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
} }
/**
* Check that the given path represents a valid class file name.
* The check is fairly cursory, checking that:
* <ul>
* <li> the name ends with .class</li>
* <li> it isn't a dot file or in a hidden directory </li>
* <li> the name of the class at least begins with a valid identifier for a class name </li>
* </ul>
* @param path
* @return
*/
private boolean isValidClassFileName (String name)
{
//no name cannot be valid
if (name == null || name.length()==0)
return false;
//skip anything that is not a class file
if (!name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
{
if (LOG.isDebugEnabled()) LOG.debug("Not a class: {}",name);
return false;
}
//skip any classfiles that are not a valid java identifier
int c0 = 0;
int ldir = name.lastIndexOf('/', name.length()-6);
c0 = (ldir > -1 ? ldir+1 : c0);
if (!Character.isJavaIdentifierStart(name.charAt(c0)))
{
if (LOG.isDebugEnabled()) LOG.debug("Not a java identifier: {}"+name);
return false;
}
return true;
}
/**
* Check that the given path does not contain hidden directories
*
* @param path
* @return
*/
private boolean isValidClassFilePath (String path)
{
//no path is not valid
if (path == null || path.length()==0)
return false;
//skip any classfiles that are in a hidden directory
if (path.startsWith(".") || path.contains("/."))
{
if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: {}"+path);
return false;
}
return true;
}
} }

View File

@ -18,11 +18,25 @@
package org.eclipse.jetty.annotations; package org.eclipse.jetty.annotations;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.AnnotationParser.Value; import org.eclipse.jetty.annotations.AnnotationParser.Value;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -31,6 +45,46 @@ import static org.junit.Assert.fail;
public class TestAnnotationParser public class TestAnnotationParser
{ {
@Rule
public TestingDir testdir = new TestingDir();
public static class TrackingAnnotationHandler implements DiscoverableAnnotationHandler
{
public final Set<String> foundClasses;
public TrackingAnnotationHandler()
{
this.foundClasses = new HashSet<String>();
}
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
List<Value> values)
{
foundClasses.add(className);
}
public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
List<Value> values)
{
/* ignore */
}
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
List<Value> values)
{
/* ignore */
}
}
@Test @Test
public void testSampleAnnotation() throws Exception public void testSampleAnnotation() throws Exception
{ {
@ -48,35 +102,20 @@ public class TestAnnotationParser
} }
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
List<Value> values) List<Value> values)
{ {
assertEquals ("m", fieldName); assertEquals ("m", fieldName);
assertEquals (org.objectweb.asm.Type.OBJECT, org.objectweb.asm.Type.getType(fieldType).getSort()); assertEquals (org.objectweb.asm.Type.OBJECT, org.objectweb.asm.Type.getType(fieldType).getSort());
assertEquals (1, values.size()); assertEquals (1, values.size());
Value anv1 = values.get(0); Value anv1 = values.get(0);
assertEquals ("value", anv1.getName()); assertEquals ("value", anv1.getName());
assertEquals (7, anv1.getValue()); assertEquals (7, anv1.getValue());
} }
public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation, public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
List<Value> values) List<Value> values)
{ {
System.err.println("Sample annotated method : classname="+className+" methodName="+methodName+" access="+access+" desc="+desc+" signature="+signature);
org.objectweb.asm.Type retType = org.objectweb.asm.Type.getReturnType(desc);
System.err.println("REturn type = "+retType);
org.objectweb.asm.Type[] params = org.objectweb.asm.Type.getArgumentTypes(desc);
if (params == null)
System.err.println("No params");
else
System.err.println(params.length+" params");
if (exceptions == null)
System.err.println("No exceptions");
else
System.err.println(exceptions.length+" exceptions");
assertEquals("org.eclipse.jetty.annotations.ClassA", className); assertEquals("org.eclipse.jetty.annotations.ClassA", className);
assertTrue(methods.contains(methodName)); assertTrue(methods.contains(methodName));
assertEquals("org.eclipse.jetty.annotations.Sample", annotation); assertEquals("org.eclipse.jetty.annotations.Sample", annotation);
@ -100,8 +139,7 @@ public class TestAnnotationParser
}); });
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
//System.err.println("Time to parse class: "+((end-start)));
System.err.println("Time to parse class: "+((end-start)));
} }
@Test @Test
@ -116,11 +154,6 @@ public class TestAnnotationParser
List<Value> values) List<Value> values)
{ {
assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className)); assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
for (Value anv: values)
{
System.err.println(anv.toString());
}
} }
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
@ -135,14 +168,73 @@ public class TestAnnotationParser
{ {
assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className)); assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
assertTrue("a".equals(methodName)); assertTrue("a".equals(methodName));
for (Value anv: values)
{
System.err.println(anv.toString());
}
} }
} }
parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Multi", new MultiAnnotationHandler()); parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Multi", new MultiAnnotationHandler());
parser.parse(classNames, null); parser.parse(classNames, null);
} }
@Test
public void testHiddenFilesInJar () throws Exception
{
File badClassesJar = MavenTestingUtils.getTestResourceFile("bad-classes.jar");
AnnotationParser parser = new AnnotationParser();
parser.parse(badClassesJar.toURI(), null);
//only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here
}
@Test
public void testBasedirExclusion() throws Exception
{
// Build up basedir, which itself has a path segment that violates java package and classnaming.
// The basedir should have no effect on annotation scanning.
// Intentionally using a base director name that starts with a "."
// This mimics what you see in jenkins, hudson, hadoop, solr, camel, and selenium for their
// installed and/or managed webapps
File basedir = testdir.getFile(".base/workspace/classes");
FS.ensureEmpty(basedir);
// Copy in class that is known to have annotations.
copyClass(ClassA.class,basedir);
// Setup Tracker
TrackingAnnotationHandler tracker = new TrackingAnnotationHandler();
// Setup annotation scanning
AnnotationParser parser = new AnnotationParser();
parser.registerAnnotationHandler(Sample.class.getName(), tracker);
// Parse
parser.parse(Resource.newResource(basedir),null);
// Validate
assertTrue(tracker.foundClasses.contains(ClassA.class.getName()));
}
private void copyClass(Class<?> clazz, File basedir) throws IOException
{
String classname = clazz.getName().replace('.',File.separatorChar) + ".class";
URL url = this.getClass().getResource('/'+classname);
assertTrue(url != null);
String classpath = classname.substring(0,classname.lastIndexOf(File.separatorChar));
FS.ensureDirExists(new File(basedir,classpath));
InputStream in = null;
OutputStream out = null;
try
{
in = url.openStream();
out = new FileOutputStream(new File(basedir,classname));
IO.copy(in,out);
}
finally
{
IO.close(out);
IO.close(in);
}
}
} }

Binary file not shown.

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.deploy.ConfigurationManager;
import org.eclipse.jetty.deploy.util.FileID; import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration; import org.eclipse.jetty.xml.XmlConfiguration;
/** Context directory App Provider. /** Context directory App Provider.
@ -37,6 +38,8 @@ import org.eclipse.jetty.xml.XmlConfiguration;
public class ContextProvider extends ScanningAppProvider public class ContextProvider extends ScanningAppProvider
{ {
private ConfigurationManager _configurationManager; private ConfigurationManager _configurationManager;
private boolean _parentLoaderPriority = false;
private String _defaultsDescriptor;
public ContextProvider() public ContextProvider()
{ {
@ -79,7 +82,22 @@ public class ContextProvider extends ScanningAppProvider
if (resource.exists() && FileID.isXmlFile(file)) if (resource.exists() && FileID.isXmlFile(file))
{ {
XmlConfiguration xmlc = new XmlConfiguration(resource.getURL()); XmlConfiguration xmlc = new XmlConfiguration(resource.getURL())
{
@Override
public void initializeDefaults(Object context)
{
super.initializeDefaults(context);
if (context instanceof WebAppContext)
{
WebAppContext webapp = (WebAppContext)context;
webapp.setParentLoaderPriority(_parentLoaderPriority);
if (_defaultsDescriptor!=null)
webapp.setDefaultsDescriptor(_defaultsDescriptor);
}
}
};
xmlc.getIdMap().put("Server",getDeploymentManager().getServer()); xmlc.getIdMap().put("Server",getDeploymentManager().getServer());
if (getConfigurationManager() != null) if (getConfigurationManager() != null)
@ -90,4 +108,43 @@ public class ContextProvider extends ScanningAppProvider
throw new IllegalStateException("App resouce does not exist "+resource); throw new IllegalStateException("App resouce does not exist "+resource);
} }
/* ------------------------------------------------------------ */
/** Get the parentLoaderPriority.
* @return the parentLoaderPriority
*/
public boolean isParentLoaderPriority()
{
return _parentLoaderPriority;
}
/* ------------------------------------------------------------ */
/** Set the parentLoaderPriority.
* <p>If the context created is a WebAppContext, then set the
* default value for {@link WebAppContext#setParentLoaderPriority(boolean)}.
* @param parentLoaderPriority the parentLoaderPriority to set
*/
public void setParentLoaderPriority(boolean parentLoaderPriority)
{
_parentLoaderPriority = parentLoaderPriority;
}
/* ------------------------------------------------------------ */
/** Get the defaultsDescriptor.
* @return the defaultsDescriptor
*/
public String getDefaultsDescriptor()
{
return _defaultsDescriptor;
}
/* ------------------------------------------------------------ */
/** Set the defaultsDescriptor.
* <p>If the context created is a WebAppContext, then set the
* default value for {@link WebAppContext#setDefaultsDescriptor(String)}
* @param defaultsDescriptor the defaultsDescriptor to set
*/
public void setDefaultsDescriptor(String defaultsDescriptor)
{
_defaultsDescriptor = defaultsDescriptor;
}
} }

View File

@ -1079,11 +1079,14 @@ public class HttpGenerator extends AbstractGenerator
@Override @Override
public String toString() public String toString()
{ {
Buffer header=_header;
Buffer buffer=_buffer;
Buffer content=_content;
return String.format("%s{s=%d,h=%d,b=%d,c=%d}", return String.format("%s{s=%d,h=%d,b=%d,c=%d}",
getClass().getSimpleName(), getClass().getSimpleName(),
_state, _state,
_header == null ? -1 : _header.length(), header == null ? -1 : header.length(),
_buffer == null ? -1 : _buffer.length(), buffer == null ? -1 : buffer.length(),
_content == null ? -1 : _content.length()); content == null ? -1 : content.length());
} }
} }

View File

@ -63,16 +63,57 @@ public class NoSqlSession extends AbstractSession
{ {
synchronized (this) synchronized (this)
{ {
if (_dirty==null)
_dirty=new HashSet<String>();
_dirty.add(name);
Object old = super.doPutOrRemove(name,value); Object old = super.doPutOrRemove(name,value);
if (_manager.getSavePeriod()==-2) if (_manager.getSavePeriod()==-2)
{
save(true); save(true);
}
return old; return old;
} }
} }
@Override
public void setAttribute(String name, Object value)
{
if ( updateAttribute(name,value) )
{
if (_dirty==null)
{
_dirty=new HashSet<String>();
}
_dirty.add(name);
}
}
/*
* a boolean version of the setAttribute method that lets us manage the _dirty set
*/
protected boolean updateAttribute (String name, Object value)
{
Object old=null;
synchronized (this)
{
checkValid();
old=doPutOrRemove(name,value);
}
if (value==null || !value.equals(old))
{
if (old!=null)
unbindValue(name,old);
if (value!=null)
bindValue(name,value);
_manager.doSessionAttributeListeners(this,name,old,value);
return true;
}
return false;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
protected void checkValid() throws IllegalStateException protected void checkValid() throws IllegalStateException

View File

@ -20,11 +20,10 @@ package org.eclipse.jetty.plus.jaas.spi;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
@ -46,7 +45,7 @@ public class PropertyFileLoginModule extends AbstractLoginModule
private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class); private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class);
private static Map<String, PropertyUserStore> _propertyUserStores = new HashMap<String, PropertyUserStore>(); private static ConcurrentHashMap<String, PropertyUserStore> _propertyUserStores = new ConcurrentHashMap<String, PropertyUserStore>();
private int _refreshInterval = 0; private int _refreshInterval = 0;
private String _filename = DEFAULT_FILENAME; private String _filename = DEFAULT_FILENAME;
@ -69,31 +68,35 @@ public class PropertyFileLoginModule extends AbstractLoginModule
private void setupPropertyUserStore(Map<String, ?> options) private void setupPropertyUserStore(Map<String, ?> options)
{ {
parseConfig(options);
if (_propertyUserStores.get(_filename) == null) if (_propertyUserStores.get(_filename) == null)
{ {
parseConfig(options); PropertyUserStore propertyUserStore = new PropertyUserStore();
propertyUserStore.setConfig(_filename);
propertyUserStore.setRefreshInterval(_refreshInterval);
PropertyUserStore _propertyUserStore = new PropertyUserStore(); PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore);
_propertyUserStore.setConfig(_filename); if (prev == null)
_propertyUserStore.setRefreshInterval(_refreshInterval);
LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
try
{ {
_propertyUserStore.start(); LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
}
catch (Exception e)
{
LOG.warn("Exception while starting propertyUserStore: ",e);
}
_propertyUserStores.put(_filename,_propertyUserStore); try
{
propertyUserStore.start();
}
catch (Exception e)
{
LOG.warn("Exception while starting propertyUserStore: ",e);
}
}
} }
} }
private void parseConfig(Map<String, ?> options) private void parseConfig(Map<String, ?> options)
{ {
_filename = (String)options.get("file") != null?(String)options.get("file"):DEFAULT_FILENAME; _filename = (String)options.get("file");
_filename = (_filename == null? DEFAULT_FILENAME : _filename);
String refreshIntervalString = (String)options.get("refreshInterval"); String refreshIntervalString = (String)options.get("refreshInterval");
_refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString); _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString);
} }

View File

@ -69,25 +69,28 @@ public class RequestLogHandler extends HandlerWrapper
} }
finally finally
{ {
if (continuation.isAsync()) if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
{ {
if (continuation.isInitial()) if (continuation.isAsync())
continuation.addContinuationListener(new ContinuationListener() {
{ if (continuation.isInitial())
continuation.addContinuationListener(new ContinuationListener()
public void onTimeout(Continuation continuation)
{ {
} public void onTimeout(Continuation continuation)
{
public void onComplete(Continuation continuation) }
{
_requestLog.log(baseRequest, (Response)response); public void onComplete(Continuation continuation)
} {
}); _requestLog.log(baseRequest, (Response)response);
}
});
}
else
_requestLog.log(baseRequest, (Response)response);
} }
else
_requestLog.log(baseRequest, (Response)response);
} }
} }

View File

@ -151,6 +151,7 @@ public class MultiPartFilter implements Filter
params.add(entry.getKey(),value); params.add(entry.getKey(),value);
} }
boolean badFormatLogged = false;
try try
{ {
// Get first boundary // Get first boundary
@ -160,7 +161,7 @@ public class MultiPartFilter implements Filter
throw new IOException("Missing content for multipart request"); throw new IOException("Missing content for multipart request");
line = line.trim(); line = line.trim();
boolean badFormatLogged = false;
while (line != null && !line.equals(boundary)) while (line != null && !line.equals(boundary))
{ {
if (!badFormatLogged) if (!badFormatLogged)
@ -402,6 +403,12 @@ public class MultiPartFilter implements Filter
// handle request // handle request
chain.doFilter(new Wrapper(srequest,params),response); chain.doFilter(new Wrapper(srequest,params),response);
} }
catch (IOException e)
{
if (!badFormatLogged)
LOG.warn("Badly formatted multipart request");
throw e;
}
finally finally
{ {
deleteFiles(request); deleteFiles(request);

View File

@ -752,6 +752,38 @@ public class MultipartFilterTest
assertTrue(response.getContent().contains("aaaa,bbbbb")); assertTrue(response.getContent().contains("aaaa,bbbbb"));
} }
@Test
public void testBufferOverflowNoCRLF () throws Exception
{
String boundary="XyXyXy";
// generated and parsed test
HttpTester request = new HttpTester();
HttpTester response = new HttpTester();
tester.addServlet(BoundaryServlet.class,"/testb");
tester.setAttribute("fileName", "abc");
tester.setAttribute("desc", "123");
tester.setAttribute("title", "ttt");
request.setMethod("POST");
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setURI("/context/testb");
request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
String content = "--XyXyXy";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(content.getBytes());
for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
{
baos.write('a');
}
request.setContent(baos.toString());
response.parse(tester.getResponses(request.generate()));
assertTrue(response.getContent().contains("Buffer size exceeded"));
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
}
/* /*
* see the testParameterMap test * see the testParameterMap test

View File

@ -18,6 +18,22 @@
package org.eclipse.jetty.servlets; package org.eclipse.jetty.servlets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert; import junit.framework.Assert;
import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
@ -34,23 +50,9 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.core.Is;
import org.hamcrest.core.IsEqual;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -138,7 +140,9 @@ public class ProxyServletTest
public void testBigDownloadWithSlowReader() throws Exception public void testBigDownloadWithSlowReader() throws Exception
{ {
// Create a 6 MiB file // Create a 6 MiB file
final File file = File.createTempFile("test_", null, MavenTestingUtils.getTargetTestingDir()); File targetTestingDir = MavenTestingUtils.getTargetTestingDir();
targetTestingDir.mkdir();
final File file = File.createTempFile("test_", null, targetTestingDir);
file.deleteOnExit(); file.deleteOnExit();
FileOutputStream fos = new FileOutputStream(file); FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];

View File

@ -6,9 +6,9 @@
<version>7.6.13-SNAPSHOT</version> <version>7.6.13-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>spdy-core</artifactId> <artifactId>spdy-core</artifactId>
<name>Jetty :: SPDY :: Core</name> <name>Jetty :: SPDY :: Core</name>
<url>http://www.eclipse.org/jetty</url> <url>http://www.eclipse.org/jetty</url>
<dependencies> <dependencies>
<dependency> <dependency>
@ -16,21 +16,21 @@
<artifactId>jetty-util</artifactId> <artifactId>jetty-util</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId> <artifactId>hamcrest-library</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId> <artifactId>slf4j-log4j12</artifactId>

View File

@ -64,13 +64,13 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId> <artifactId>hamcrest-library</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId> <artifactId>slf4j-log4j12</artifactId>

View File

@ -49,6 +49,10 @@ public class ReadLineInputStream extends BufferedInputStream
while (true) while (true)
{ {
int b=super.read(); int b=super.read();
if (markpos < 0)
throw new IOException("Buffer size exceeded: no line terminator");
if (b==-1) if (b==-1)
{ {
int m=markpos; int m=markpos;

View File

@ -316,7 +316,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
if (jsp_file != null) if (jsp_file != null)
{ {
holder.setForcedPath(jsp_file); holder.setForcedPath(jsp_file);
holder.setClassName(jspServletClass); //only use our default instance ServletHolder jsp=context.getServletHandler().getServlet("jsp");
if (jsp!=null)
holder.setClassName(jsp.getClassName());
} }
// handle load-on-startup // handle load-on-startup

View File

@ -87,7 +87,10 @@ public class WebInfConfiguration extends AbstractConfiguration
context.getMetaData().addContainerJar(Resource.newResource(uri)); context.getMetaData().addContainerJar(Resource.newResource(uri));
} }
}; };
ClassLoader loader = context.getClassLoader(); ClassLoader loader = null;
if (context.getClassLoader() != null)
loader = context.getClassLoader().getParent();
while (loader != null && (loader instanceof URLClassLoader)) while (loader != null && (loader instanceof URLClassLoader))
{ {
URL[] urls = ((URLClassLoader)loader).getURLs(); URL[] urls = ((URLClassLoader)loader).getURLs();

View File

@ -19,14 +19,16 @@
package org.eclipse.jetty.websocket; package org.eclipse.jetty.websocket;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.EofException;
/* ------------------------------------------------------------ */ /**
/** WebSocketGenerator. * WebSocketGenerator.
* This class generates websocket packets. * This class generates websocket packets.
* It is fully synchronized because it is likely that async * It is fully synchronized because it is likely that async
* threads will call the addMessage methods while other * threads will call the addMessage methods while other
@ -34,214 +36,263 @@ import org.eclipse.jetty.io.EofException;
*/ */
public class WebSocketGeneratorRFC6455 implements WebSocketGenerator public class WebSocketGeneratorRFC6455 implements WebSocketGenerator
{ {
final private WebSocketBuffers _buffers; private final Lock _lock = new ReentrantLock();
final private EndPoint _endp; private final WebSocketBuffers _buffers;
private final EndPoint _endp;
private final byte[] _mask = new byte[4];
private final MaskGen _maskGen;
private Buffer _buffer; private Buffer _buffer;
private final byte[] _mask=new byte[4];
private int _m; private int _m;
private boolean _opsent; private boolean _opsent;
private final MaskGen _maskGen;
private boolean _closed; private boolean _closed;
public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp) public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp)
{ {
_buffers=buffers; this(buffers, endp, null);
_endp=endp;
_maskGen=null;
} }
public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
{ {
_buffers=buffers; _buffers = buffers;
_endp=endp; _endp = endp;
_maskGen=maskGen; _maskGen = maskGen;
} }
public synchronized Buffer getBuffer() public Buffer getBuffer()
{ {
return _buffer; _lock.lock();
} try
public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
{
// System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length);
if (_closed)
throw new EofException("Closed");
if (opcode==WebSocketConnectionRFC6455.OP_CLOSE)
_closed=true;
boolean mask=_maskGen!=null;
if (_buffer==null)
_buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer();
boolean last=WebSocketConnectionRFC6455.isLastFrame(flags);
int space=mask?14:10;
do
{ {
opcode = _opsent?WebSocketConnectionRFC6455.OP_CONTINUATION:opcode; return _buffer;
opcode=(byte)(((0xf&flags)<<4)+(0xf&opcode)); }
_opsent=true; finally
{
_lock.unlock();
}
}
int payload=length; public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
if (payload+space>_buffer.capacity()) {
{ _lock.lock();
// We must fragement, so clear FIN bit try
opcode=(byte)(opcode&0x7F); // Clear the FIN bit {
payload=_buffer.capacity()-space; if (_closed)
} throw new EofException("Closed");
else if (last) if (opcode == WebSocketConnectionRFC6455.OP_CLOSE)
opcode= (byte)(opcode|0x80); // Set the FIN bit _closed = true;
// ensure there is space for header boolean mask = _maskGen != null;
if (_buffer.space() <= space)
if (_buffer == null)
_buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer();
boolean last = WebSocketConnectionRFC6455.isLastFrame(flags);
int space = mask ? 14 : 10;
do
{ {
flushBuffer(); opcode = _opsent ? WebSocketConnectionRFC6455.OP_CONTINUATION : opcode;
opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode));
_opsent = true;
int payload = length;
if (payload + space > _buffer.capacity())
{
// We must fragement, so clear FIN bit
opcode = (byte)(opcode & 0x7F); // Clear the FIN bit
payload = _buffer.capacity() - space;
}
else if (last)
opcode = (byte)(opcode | 0x80); // Set the FIN bit
// ensure there is space for header
if (_buffer.space() <= space) if (_buffer.space() <= space)
flush(); {
} flushBuffer();
if (_buffer.space() <= space)
flush();
}
// write the opcode and length // write the opcode and length
if (payload>0xffff) if (payload > 0xffff)
{ {
_buffer.put(new byte[]{ _buffer.put(new byte[]{
opcode, opcode,
mask?(byte)0xff:(byte)0x7f, mask ? (byte)0xff : (byte)0x7f,
(byte)0, (byte)0,
(byte)0, (byte)0,
(byte)0, (byte)0,
(byte)0, (byte)0,
(byte)((payload>>24)&0xff), (byte)((payload >> 24) & 0xff),
(byte)((payload>>16)&0xff), (byte)((payload >> 16) & 0xff),
(byte)((payload>>8)&0xff), (byte)((payload >> 8) & 0xff),
(byte)(payload&0xff)}); (byte)(payload & 0xff)});
} }
else if (payload >=0x7e) else if (payload >= 0x7e)
{ {
_buffer.put(new byte[]{ _buffer.put(new byte[]{
opcode, opcode,
mask?(byte)0xfe:(byte)0x7e, mask ? (byte)0xfe : (byte)0x7e,
(byte)(payload>>8), (byte)(payload >> 8),
(byte)(payload&0xff)}); (byte)(payload & 0xff)});
} }
else else
{ {
_buffer.put(new byte[]{ _buffer.put(new byte[]{
opcode, opcode,
(byte)(mask?(0x80|payload):payload)}); (byte)(mask ? (0x80 | payload) : payload)});
} }
// write mask
if (mask)
{
_maskGen.genMask(_mask);
_m=0;
_buffer.put(_mask);
}
// write payload
int remaining = payload;
while (remaining > 0)
{
_buffer.compact();
int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
// write mask
if (mask) if (mask)
{ {
for (int i=0;i<chunk;i++) _maskGen.genMask(_mask);
_buffer.put((byte)(content[offset+ (payload-remaining)+i]^_mask[+_m++%4])); _m = 0;
_buffer.put(_mask);
} }
else
_buffer.put(content, offset + (payload - remaining), chunk);
remaining -= chunk; // write payload
if (_buffer.space() > 0) int remaining = payload;
while (remaining > 0)
{ {
// Gently flush the data, issuing a non-blocking write _buffer.compact();
flushBuffer(); int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
}
else if (mask)
{ {
// Forcibly flush the data, issuing a blocking write for (int i = 0; i < chunk; i++)
flush(); _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4]));
if (remaining == 0) }
else
_buffer.put(content, offset + (payload - remaining), chunk);
remaining -= chunk;
if (_buffer.space() > 0)
{ {
// Gently flush the data, issuing a non-blocking write // Gently flush the data, issuing a non-blocking write
flushBuffer(); flushBuffer();
} }
else
{
// Forcibly flush the data, issuing a blocking write
flush();
if (remaining == 0)
{
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
}
} }
offset += payload;
length -= payload;
} }
offset+=payload; while (length > 0);
length-=payload; _opsent = !last;
}
while (length>0);
_opsent=!last;
if (_buffer!=null && _buffer.length()==0) if (_buffer != null && _buffer.length() == 0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
}
public synchronized int flushBuffer() throws IOException
{
if (!_endp.isOpen())
throw new EofException();
if (_buffer!=null)
{
int flushed=_buffer.hasContent()?_endp.flush(_buffer):0;
if (_closed&&_buffer.length()==0)
_endp.shutdownOutput();
return flushed;
}
return 0;
}
public synchronized int flush() throws IOException
{
if (_buffer==null)
return 0;
int result = flushBuffer();
if (!_endp.isBlocking())
{
long now = System.currentTimeMillis();
long end=now+_endp.getMaxIdleTime();
while (_buffer.length()>0)
{ {
boolean ready = _endp.blockWritable(end-now); _buffers.returnBuffer(_buffer);
if (!ready) _buffer = null;
{
now = System.currentTimeMillis();
if (now<end)
continue;
throw new IOException("Write timeout");
}
result += flushBuffer();
} }
} }
_buffer.compact(); finally
return result;
}
public synchronized boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
public synchronized void returnBuffer()
{
if (_buffer!=null && _buffer.length()==0)
{ {
_buffers.returnBuffer(_buffer); _lock.unlock();
_buffer=null; }
}
public int flushBuffer() throws IOException
{
if (!_lock.tryLock())
return 0;
try
{
if (!_endp.isOpen())
throw new EofException();
if (_buffer != null)
{
int flushed = _buffer.hasContent() ? _endp.flush(_buffer) : 0;
if (_closed && _buffer.length() == 0)
_endp.shutdownOutput();
return flushed;
}
return 0;
}
finally
{
_lock.unlock();
}
}
public int flush() throws IOException
{
if (!_lock.tryLock())
return 0;
try
{
if (_buffer == null)
return 0;
int result = flushBuffer();
if (!_endp.isBlocking())
{
long now = System.currentTimeMillis();
long end = now + _endp.getMaxIdleTime();
while (_buffer.length() > 0)
{
boolean ready = _endp.blockWritable(end - now);
if (!ready)
{
now = System.currentTimeMillis();
if (now < end)
continue;
throw new IOException("Write timeout");
}
result += flushBuffer();
}
}
_buffer.compact();
return result;
}
finally
{
_lock.unlock();
}
}
public boolean isBufferEmpty()
{
_lock.lock();
try
{
return _buffer == null || _buffer.length() == 0;
}
finally
{
_lock.unlock();
}
}
public void returnBuffer()
{
_lock.lock();
try
{
if (_buffer != null && _buffer.length() == 0)
{
_buffers.returnBuffer(_buffer);
_buffer = null;
}
}
finally
{
_lock.unlock();
} }
} }

View File

@ -19,7 +19,6 @@
package org.eclipse.jetty.xml; package org.eclipse.jetty.xml;
import java.net.URL; import java.net.URL;
import java.util.Map;
/** /**
* A ConfigurationProcessor for non XmlConfiguration format files. * A ConfigurationProcessor for non XmlConfiguration format files.
@ -32,7 +31,7 @@ import java.util.Map;
*/ */
public interface ConfigurationProcessor public interface ConfigurationProcessor
{ {
public void init(URL url, XmlParser.Node config, Map<String, Object> idMap, Map<String, String> properties); public void init(URL url, XmlParser.Node root, XmlConfiguration configuration);
public Object configure( Object obj) throws Exception; public Object configure( Object obj) throws Exception;
public Object configure() throws Exception; public Object configure() throws Exception;

View File

@ -227,7 +227,7 @@ public class XmlConfiguration
{ {
throw new IllegalArgumentException("Unknown XML tag:"+config.getTag()); throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
} }
_processor.init(_url,config,_idMap, _propertyMap); _processor.init(_url,config,this);
} }
@ -296,51 +296,63 @@ public class XmlConfiguration
return _processor.configure(); return _processor.configure();
} }
/* ------------------------------------------------------------ */
/** Initialize a new Object defaults.
* <p>This method must be called by any {@link ConfigurationProcessor} when it
* creates a new instance of an object before configuring it, so that a derived
* XmlConfiguration class may inject default values.
* @param object
*/
public void initializeDefaults(Object object)
{
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private static class JettyXmlConfiguration implements ConfigurationProcessor private static class JettyXmlConfiguration implements ConfigurationProcessor
{ {
XmlParser.Node _config; XmlParser.Node _root;
Map<String, Object> _idMap; XmlConfiguration _configuration;
Map<String, String> _propertyMap;
public void init(URL url, XmlParser.Node config, Map<String, Object> idMap, Map<String, String> properties) public void init(URL url, XmlParser.Node root, XmlConfiguration configuration)
{ {
_config=config; _root=root;
_idMap=idMap; _configuration=configuration;
_propertyMap=properties;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public Object configure(Object obj) throws Exception public Object configure(Object obj) throws Exception
{ {
// Check the class of the object // Check the class of the object
Class<?> oClass = nodeClass(_config); Class<?> oClass = nodeClass(_root);
if (oClass != null && !oClass.isInstance(obj)) if (oClass != null && !oClass.isInstance(obj))
{ {
String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders."; String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders); throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders);
} }
configure(obj,_config,0); configure(obj,_root,0);
return obj; return obj;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public Object configure() throws Exception public Object configure() throws Exception
{ {
Class<?> oClass = nodeClass(_config); Class<?> oClass = nodeClass(_root);
String id = _config.getAttribute("id"); String id = _root.getAttribute("id");
Object obj = id == null?null:_idMap.get(id); Object obj = id == null?null:_configuration.getIdMap().get(id);
if (obj == null && oClass != null) if (obj == null && oClass != null)
{
obj = oClass.newInstance(); obj = oClass.newInstance();
_configuration.initializeDefaults(obj);
}
if (oClass != null && !oClass.isInstance(obj)) if (oClass != null && !oClass.isInstance(obj))
throw new ClassCastException(oClass.toString()); throw new ClassCastException(oClass.toString());
configure(obj,_config,0); configure(obj,_root,0);
return obj; return obj;
} }
@ -368,7 +380,7 @@ public class XmlConfiguration
{ {
String id = cfg.getAttribute("id"); String id = cfg.getAttribute("id");
if (id != null) if (id != null)
_idMap.put(id,obj); _configuration.getIdMap().put(id,obj);
for (; i < cfg.size(); i++) for (; i < cfg.size(); i++)
{ {
@ -558,6 +570,7 @@ public class XmlConfiguration
} }
Constructor<?> cons = sClass.getConstructor(vClass); Constructor<?> cons = sClass.getConstructor(vClass);
arg[0] = cons.newInstance(arg); arg[0] = cons.newInstance(arg);
_configuration.initializeDefaults(arg[0]);
set.invoke(obj,arg); set.invoke(obj,arg);
return; return;
} }
@ -675,7 +688,7 @@ public class XmlConfiguration
} }
} }
if (id != null) if (id != null)
_idMap.put(id,obj); _configuration.getIdMap().put(id,obj);
return obj; return obj;
} }
@ -730,7 +743,7 @@ public class XmlConfiguration
{ {
Object n= TypeUtil.call(oClass,method,obj,arg); Object n= TypeUtil.call(oClass,method,obj,arg);
if (id != null) if (id != null)
_idMap.put(id,n); _configuration.getIdMap().put(id,n);
configure(n,node,argi); configure(n,node,argi);
return n; return n;
} }
@ -792,6 +805,7 @@ public class XmlConfiguration
try try
{ {
n = constructors[c].newInstance(arg); n = constructors[c].newInstance(arg);
_configuration.initializeDefaults(n);
called = true; called = true;
} }
catch (IllegalAccessException e) catch (IllegalAccessException e)
@ -809,7 +823,7 @@ public class XmlConfiguration
if (called) if (called)
{ {
if (id != null) if (id != null)
_idMap.put(id,n); _configuration.getIdMap().put(id,n);
configure(n,node,argi); configure(n,node,argi);
return n; return n;
} }
@ -827,7 +841,7 @@ public class XmlConfiguration
private Object refObj(Object obj, XmlParser.Node node) throws Exception private Object refObj(Object obj, XmlParser.Node node) throws Exception
{ {
String id = node.getAttribute("id"); String id = node.getAttribute("id");
obj = _idMap.get(id); obj = _configuration.getIdMap().get(id);
if (obj == null) if (obj == null)
throw new IllegalStateException("No object for id=" + id); throw new IllegalStateException("No object for id=" + id);
configure(obj,node,0); configure(obj,node,0);
@ -870,12 +884,12 @@ public class XmlConfiguration
Object v = value(obj,item); Object v = value(obj,item);
al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v); al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v);
if (nid != null) if (nid != null)
_idMap.put(nid,v); _configuration.getIdMap().put(nid,v);
} }
Object array = LazyList.toArray(al,aClass); Object array = LazyList.toArray(al,aClass);
if (id != null) if (id != null)
_idMap.put(id,array); _configuration.getIdMap().put(id,array);
return array; return array;
} }
@ -889,7 +903,7 @@ public class XmlConfiguration
Map<Object, Object> map = new HashMap<Object, Object>(); Map<Object, Object> map = new HashMap<Object, Object>();
if (id != null) if (id != null)
_idMap.put(id,map); _configuration.getIdMap().put(id,map);
for (Object o : node) for (Object o : node)
{ {
@ -925,9 +939,9 @@ public class XmlConfiguration
map.put(k,v); map.put(k,v);
if (kid != null) if (kid != null)
_idMap.put(kid,k); _configuration.getIdMap().put(kid,k);
if (vid != null) if (vid != null)
_idMap.put(vid,v); _configuration.getIdMap().put(vid,v);
} }
return map; return map;
@ -947,12 +961,13 @@ public class XmlConfiguration
String name = node.getAttribute("name"); String name = node.getAttribute("name");
String defaultValue = node.getAttribute("default"); String defaultValue = node.getAttribute("default");
Object prop; Object prop;
if (_propertyMap != null && _propertyMap.containsKey(name)) Map<String,String> property_map=_configuration.getProperties();
prop = _propertyMap.get(name); if (property_map != null && property_map.containsKey(name))
prop = property_map.get(name);
else else
prop = defaultValue; prop = defaultValue;
if (id != null) if (id != null)
_idMap.put(id,prop); _configuration.getIdMap().put(id,prop);
if (prop != null) if (prop != null)
configure(prop,node,0); configure(prop,node,0);
return prop; return prop;
@ -975,7 +990,7 @@ public class XmlConfiguration
String ref = node.getAttribute("ref"); String ref = node.getAttribute("ref");
if (ref != null) if (ref != null)
{ {
value = _idMap.get(ref); value = _configuration.getIdMap().get(ref);
} }
else else
{ {

View File

@ -34,6 +34,7 @@ public class TestConfiguration extends HashMap<String,Object>
public static int VALUE=77; public static int VALUE=77;
public TestConfiguration nested; public TestConfiguration nested;
public String testString="default";
public Object testObject; public Object testObject;
public int testInt; public int testInt;
public URL url; public URL url;
@ -65,6 +66,25 @@ public class TestConfiguration extends HashMap<String,Object>
propValue=value; propValue=value;
} }
public TestConfiguration getNested()
{
return nested;
}
public void setNested(TestConfiguration nested)
{
this.nested = nested;
}
public String getTestString()
{
return testString;
}
public void setTestString(String testString)
{
this.testString = testString;
}
public void call() public void call()
{ {
@ -73,7 +93,6 @@ public class TestConfiguration extends HashMap<String,Object>
public TestConfiguration call(Boolean b) public TestConfiguration call(Boolean b)
{ {
nested=new TestConfiguration();
nested.put("Arg",b); nested.put("Arg",b);
return nested; return nested;
} }

View File

@ -21,11 +21,12 @@ package org.eclipse.jetty.xml;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.nullValue;
@ -127,10 +128,28 @@ public class XmlConfigurationTest
properties.put("whatever", "xxx"); properties.put("whatever", "xxx");
URL url = XmlConfigurationTest.class.getClassLoader().getResource(_configure); URL url = XmlConfigurationTest.class.getClassLoader().getResource(_configure);
XmlConfiguration configuration = new XmlConfiguration(url); final AtomicInteger count = new AtomicInteger(0);
XmlConfiguration configuration = new XmlConfiguration(url)
{
@Override
public void initializeDefaults(Object object)
{
if (object instanceof TestConfiguration)
{
count.incrementAndGet();
((TestConfiguration)object).setNested(null);
((TestConfiguration)object).setTestString("NEW DEFAULT");
}
}
};
configuration.getProperties().putAll(properties); configuration.getProperties().putAll(properties);
TestConfiguration tc = (TestConfiguration)configuration.configure(); TestConfiguration tc = (TestConfiguration)configuration.configure();
assertEquals(3,count.get());
assertEquals("NEW DEFAULT",tc.getTestString());
assertEquals("nested",tc.getNested().getTestString());
assertEquals("NEW DEFAULT",tc.getNested().getNested().getTestString());
assertEquals("Set String","SetValue",tc.testObject); assertEquals("Set String","SetValue",tc.testObject);
assertEquals("Set Type",2,tc.testInt); assertEquals("Set Type",2,tc.testInt);

View File

@ -83,6 +83,16 @@
<Put name="Float" type="Float">2.3</Put> <Put name="Float" type="Float">2.3</Put>
<Put name="Env"><Env name="HOME"/></Put> <Put name="Env"><Env name="HOME"/></Put>
<Set name="nested">
<New class="org.eclipse.jetty.xml.TestConfiguration">
<Set name="testString">nested</Set>
<Set name="nested">
<New class="org.eclipse.jetty.xml.TestConfiguration">
</New>
</Set>
</New>
</Set>
<Call name="call"> <Call name="call">
</Call> </Call>

17
pom.xml
View File

@ -508,7 +508,7 @@
<dependency> <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId> <groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId> <artifactId>jetty-test-helper</artifactId>
<version>2.0</version> <version>2.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
@ -528,17 +528,22 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.8.1</version> <version>4.11</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId> <artifactId>hamcrest-core</artifactId>
<version>1.1</version> <version>1.3</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>1.8.5</version> <version>1.9.5</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -99,7 +99,7 @@
<plugin> <plugin>
<groupId>org.mortbay.jetty</groupId> <groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId> <artifactId>jetty-maven-plugin</artifactId>
<version>7.5.2-SNAPSHOT</version> <version>${project.version}</version>
<configuration> <configuration>
<stopPort>8087</stopPort> <stopPort>8087</stopPort>
<stopKey>foo</stopKey> <stopKey>foo</stopKey>

View File

@ -18,21 +18,18 @@
package org.eclipse.jetty.test.monitor; package org.eclipse.jetty.test.monitor;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.management.MBeanServerConnection;
import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.monitor.JMXMonitor; import org.eclipse.jetty.monitor.JMXMonitor;
import org.eclipse.jetty.toolchain.jmx.JmxServiceConnection; import org.eclipse.jetty.test.support.JettyDistro;
import org.eclipse.jetty.toolchain.test.JettyDistro;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;

View File

@ -18,15 +18,13 @@
package org.eclipse.jetty.test.monitor; package org.eclipse.jetty.test.monitor;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import javax.management.MBeanServerConnection;
import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpMethods;
@ -40,8 +38,7 @@ import org.eclipse.jetty.monitor.jmx.MonitorAction;
import org.eclipse.jetty.monitor.triggers.GreaterThanAttrEventTrigger; import org.eclipse.jetty.monitor.triggers.GreaterThanAttrEventTrigger;
import org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger; import org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger;
import org.eclipse.jetty.monitor.triggers.OrEventTrigger; import org.eclipse.jetty.monitor.triggers.OrEventTrigger;
import org.eclipse.jetty.toolchain.jmx.JmxServiceConnection; import org.eclipse.jetty.test.support.JettyDistro;
import org.eclipse.jetty.toolchain.test.JettyDistro;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.util.thread.ExecutorThreadPool;

View File

@ -18,7 +18,7 @@
package org.eclipse.jetty.test.monitor; package org.eclipse.jetty.test.monitor;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.util.Random; import java.util.Random;
@ -30,7 +30,7 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.monitor.JMXMonitor; import org.eclipse.jetty.monitor.JMXMonitor;
import org.eclipse.jetty.toolchain.test.JettyDistro; import org.eclipse.jetty.test.support.JettyDistro;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;

View File

@ -0,0 +1,874 @@
//
// ========================================================================
// 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.test.support;
//
//========================================================================
//------------------------------------------------------------------------
//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.
//========================================================================
//
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.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.JAR;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.PathAssert;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.junit.Assert;
/**
* Basic process based executor for using the Jetty Distribution along with custom configurations to perform basic
* <p>
* Allows for a test specific directory, that is a copied jetty-distribution, and then modified for the test specific testing required.
* <p>
* Requires that you setup the maven-dependency-plugin appropriately for the base distribution you want to use, along with any other dependencies (wars, libs,
* etc..) that you may need from other maven projects.
* <p>
* Maven Dependency Plugin Setup:
*
* <pre>
* &lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
* xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;
*
* &lt;!-- Common Destination Directories --&gt;
*
* &lt;properties&gt;
* &lt;test-wars-dir&gt;${project.build.directory}/test-wars&lt;/test-wars-dir&gt;
* &lt;test-libs-dir&gt;${project.build.directory}/test-libs&lt;/test-libs-dir&gt;
* &lt;test-distro-dir&gt;${project.build.directory}/test-dist&lt;/test-distro-dir&gt;
* &lt;/properties&gt;
*
* &lt;build&gt;
* &lt;plugins&gt;
* &lt;plugin&gt;
* &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
* &lt;artifactId&gt;maven-dependency-plugin&lt;/artifactId&gt;
* &lt;version&gt;2.1&lt;/version&gt;
* &lt;executions&gt;
*
* &lt;!-- Copy LIB and WAR dependencies into place that JettyDistro can use them --&gt;
*
* &lt;execution&gt;
* &lt;id&gt;test-lib-war-copy&lt;/id&gt;
* &lt;phase&gt;process-test-resources&lt;/phase&gt;
* &lt;goals&gt;
* &lt;goal&gt;copy&lt;/goal&gt;
* &lt;/goals&gt;
* &lt;configuration&gt;
* &lt;artifactItems&gt;
* &lt;artifactItem&gt;
* &lt;groupId&gt;org.mortbay.jetty.testwars&lt;/groupId&gt;
* &lt;artifactId&gt;test-war-java_util_logging&lt;/artifactId&gt;
* &lt;version&gt;7.3.0&lt;/version&gt;
* &lt;type&gt;war&lt;/type&gt;
* &lt;outputDirectory&gt;${test-wars-dir}&lt;/outputDirectory&gt;
* &lt;/artifactItem&gt;
* &lt;artifactItem&gt;
* &lt;groupId&gt;org.mortbay.jetty&lt;/groupId&gt;
* &lt;artifactId&gt;jetty-aspect-servlet-api-2.5&lt;/artifactId&gt;
* &lt;version&gt;7.3.0&lt;/version&gt;
* &lt;type&gt;jar&lt;/type&gt;
* &lt;outputDirectory&gt;${test-libs-dir}&lt;/outputDirectory&gt;
* &lt;/artifactItem&gt;
* &lt;/artifactItems&gt;
* &lt;overWriteIfNewer&gt;true&lt;/overWriteIfNewer&gt;
* &lt;overWrite&gt;true&lt;/overWrite&gt;
* &lt;stripVersion&gt;true&lt;/stripVersion&gt;
* &lt;/configuration&gt;
* &lt;/execution&gt;
*
* &lt;!-- Extract Jetty DISTRIBUTION into place that JettyDistro can use it --&gt;
*
* &lt;execution&gt;
* &lt;id&gt;unpack-test-dist&lt;/id&gt;
* &lt;phase&gt;process-test-resources&lt;/phase&gt;
* &lt;goals&gt;
* &lt;goal&gt;unpack&lt;/goal&gt;
* &lt;/goals&gt;
* &lt;configuration&gt;
* &lt;artifactItems&gt;
* &lt;artifactItem&gt;
* &lt;groupId&gt;org.eclipse.jetty&lt;/groupId&gt;
* &lt;artifactId&gt;jetty-distribution&lt;/artifactId&gt;
* &lt;version&gt;7.3.0&lt;/version&gt;
* &lt;type&gt;zip&lt;/type&gt;
* &lt;overWrite&gt;true&lt;/overWrite&gt;
* &lt;/artifactItem&gt;
* &lt;/artifactItems&gt;
* &lt;outputAbsoluteArtifactFilename&gt;true&lt;/outputAbsoluteArtifactFilename&gt;
* &lt;outputDirectory&gt;${test-distro-dir}&lt;/outputDirectory&gt;
* &lt;overWriteSnapshots&gt;true&lt;/overWriteSnapshots&gt;
* &lt;overWriteIfNewer&gt;true&lt;/overWriteIfNewer&gt;
* &lt;/configuration&gt;
* &lt;/execution&gt;
* &lt;/executions&gt;
* &lt;/plugin&gt;
* &lt;/plugins&gt;
* &lt;/build&gt;
*
* &lt;/project&gt;
* </pre>
* <p>
* If you have a specific configuration you want to setup, you'll want to prepare this configuration in an overlay directory underneath the
* <code>src/test/resources/</code> directory. <br>
* Notes:
* <ol>
* <li>The {@link JettyDistro} sets up a unique test directory (based on the constructor {@link #JettyDistro(Class)} or {@link #JettyDistro(TestingDir)}), by
* ensuring the directory is empty, then copying the <code>target/test-dist</code> directory into this new testing directory prior to the test specific changes
* to the configuration.<br>
* Note: this testing directory is a complete jetty distribution, suitable for executing via the command line for additional testing needs.</li>
* <li>The directory name you choose in <code>src/test/resources</code> will be the name you use in the {@link #overlayConfig(String)} method to provide
* replacement configurations for the Jetty Distribution.</li>
* <li>You'll want to {@link #delete(String)} any files and/or directories from the standard distribution prior to using the {@link #overlayConfig(String)}
* method.</li>
* <li>Use the {@link #copyLib(String, String)} method to copy JAR files from the <code>target/test-libs</code> directory (created and managed above using the
* <code>maven-dependency-plugin</code>) to copy the lib into the test specific.</li>
* <li>Use the {@link #copyTestWar(String)} method to copy WAR files from the <code>target/test-wars</code> directory (created and managed above using the
* <code>maven-dependency-plugin</code>) to copy the WAR into the test specific directory.</li>
* </ol>
* <p>
* Next you'll want to use Junit 4.8+ and the <code>&#064;BeforeClass</code> and <code>&#064;AfterClass</code> annotations to setup the <code>JettyDistro</code>
* class for setting up your testing configuration.
* <p>
* Example Test Case using {@link JettyDistro} class
*
* <pre>
* public class MySampleTest
* {
* private static JettyDistro jetty;
*
* &#064;BeforeClass
* public static void initJetty() throws Exception
* {
* jetty = new JettyDistro(MySampleTest.class);
*
* jetty.copyTestWar(&quot;test-war-java_util_logging.war&quot;);
* jetty.copyTestWar(&quot;test-war-policy.war&quot;);
*
* jetty.delete(&quot;webapps/test.war&quot;);
* jetty.delete(&quot;contexts/test.d&quot;);
* jetty.delete(&quot;contexts/javadoc.xml&quot;);
* jetty.delete(&quot;contexts/test.xml&quot;);
*
* jetty.overlayConfig(&quot;no_security&quot;);
*
* jetty.setDebug(true);
*
* jetty.start();
* }
*
* &#064;AfterClass
* public static void shutdownJetty() throws Exception
* {
* if (jetty != null)
* {
* jetty.stop();
* }
* }
*
* &#064;Test
* public void testRequest() throws Exception
* {
* SimpleRequest request = new SimpleRequest(jetty.getBaseUri());
* String path = &quot;/test-war-policy/security/PRACTICAL/testFilsystem&quot;);
* String response = request.getString(path);
* Assert.assertEquals(&quot;Success&quot;, response);
* }
* }
* </pre>
*/
public class JettyDistro
{
private String artifactName = "jetty-distribution";
private long startTime = 60;
private TimeUnit timeUnit = TimeUnit.SECONDS;
private File jettyHomeDir;
private Process pid;
private URI baseUri;
private String jmxUrl;
private boolean _debug = false;
/**
* Setup the JettyHome as belonging in a testing directory associated with a testing clazz.
*
* @param clazz
* the testing class using this JettyDistro
* @throws IOException
* if unable to copy unpacked distribution into place for the provided testing directory
*/
public JettyDistro(Class<?> clazz) throws IOException
{
this(clazz,null);
}
/**
* Setup the JettyHome as belonging in a testing directory associated with a testing clazz.
*
* @param clazz
* the testing class using this JettyDistro
* @param artifact
* name of jetty distribution artifact
* @throws IOException
* if unable to copy unpacked distribution into place for the provided testing directory
*/
public JettyDistro(Class<?> clazz, String artifact) throws IOException
{
this.jettyHomeDir = MavenTestingUtils.getTargetTestingDir(clazz,"jettyHome");
if (artifact != null)
{
this.artifactName = artifact;
}
copyBaseDistro();
}
/**
* Setup the JettyHome as belonging to a specific testing method directory
*
* @param testdir
* the testing directory to use as the JettyHome for this JettyDistro
* @throws IOException
* if unable to copy unpacked distribution into place for the provided testing directory
*/
public JettyDistro(TestingDir testdir) throws IOException
{
this.jettyHomeDir = testdir.getDir();
copyBaseDistro();
}
/**
* Setup the JettyHome as belonging to a specific testing method directory
*
* @param testdir
* the testing directory to use as the JettyHome for this JettyDistro
* @param artifact
* name of jetty distribution artifact
* @throws IOException
* if unable to copy unpacked distribution into place for the provided testing directory
*/
public JettyDistro(TestingDir testdir, String artifact) throws IOException
{
this.jettyHomeDir = testdir.getDir();
if (artifact != null)
{
this.artifactName = artifact;
}
copyBaseDistro();
}
/**
*
* @throws IOException
* if unable to copy unpacked distribution into place for the provided testing directory
*/
private void copyBaseDistro() throws IOException
{
// The outputDirectory for the maven side dependency:unpack goal.
File distroUnpackDir = MavenTestingUtils.getTargetFile("test-dist");
PathAssert.assertDirExists(artifactName + " dependency:unpack",distroUnpackDir);
// The actual jetty-distribution-${version} directory is under this directory.
// Lets find it.
File subdirs[] = distroUnpackDir.listFiles(new FileFilter()
{
public boolean accept(File path)
{
if (!path.isDirectory())
{
return false;
}
return path.getName().startsWith(artifactName + "-");
}
});
if (subdirs.length == 0)
{
// No jetty-distribution found.
StringBuilder err = new StringBuilder();
err.append("No target/test-dist/");
err.append(artifactName);
err.append("-${version} directory found.");
err.append("\n To fix this, run 'mvn process-test-resources' to create the directory.");
throw new IOException(err.toString());
}
if (subdirs.length != 1)
{
// Too many jetty-distributions found.
StringBuilder err = new StringBuilder();
err.append("Too many target/test-dist/");
err.append(artifactName);
err.append("-${version} directories found.");
for (File dir : subdirs)
{
err.append("\n ").append(dir.getAbsolutePath());
}
err.append("\n To fix this, run 'mvn clean process-test-resources' to recreate the target/test-dist directory.");
throw new IOException(err.toString());
}
File distroSrcDir = subdirs[0];
FS.ensureEmpty(jettyHomeDir);
System.out.printf("Copying Jetty Distribution: %s%n",distroSrcDir.getAbsolutePath());
System.out.printf(" To Testing Dir: %s%n",jettyHomeDir.getAbsolutePath());
IO.copyDir(distroSrcDir,jettyHomeDir);
}
/**
* Return the $(jetty.home) directory being used for this JettyDistro
*
* @return the jetty.home directory being used
*/
public File getJettyHomeDir()
{
return this.jettyHomeDir;
}
/**
* Copy a war file from ${project.basedir}/target/test-wars/${testWarFilename} into the ${jetty.home}/webapps/ directory
*
* @param testWarFilename
* the war file to copy (must exist)
* @throws IOException
* if unable to copy the war file.
*/
public void copyTestWar(String testWarFilename) throws IOException
{
File srcWar = MavenTestingUtils.getTargetFile("test-wars/" + testWarFilename);
File destWar = new File(jettyHomeDir,OS.separators("webapps/" + testWarFilename));
FS.ensureDirExists(destWar.getParentFile());
IO.copyFile(srcWar,destWar);
}
/**
* Copy an arbitrary file from <code>src/test/resources/${resourcePath}</code> to the testing directory.
*
* @param resourcePath
* the relative path for file content within the <code>src/test/resources</code> directory.
* @param outputPath
* the testing directory relative output path for the file output (will result in a file with the outputPath name being created)
* @throws IOException
* if unable to copy resource file
*/
public void copyResource(String resourcePath, String outputPath) throws IOException
{
File srcFile = MavenTestingUtils.getTestResourceFile(resourcePath);
File destFile = new File(jettyHomeDir,OS.separators(outputPath));
FS.ensureDirExists(destFile.getParentFile());
IO.copyFile(srcFile,destFile);
}
/**
* Copy an arbitrary file from <code>target/test-libs/${libFilename}</code> to the testing directory.
*
* @param libFilename
* the <code>target/test-libs/${libFilename}</code> to copy
* @param outputPath
* the destination testing directory relative output path for the lib. (will result in a file with the outputPath name being created)
* @throws IOException
* if unable to copy lib
*/
public void copyLib(String libFilename, String outputPath) throws IOException
{
File srcLib = MavenTestingUtils.getTargetFile("test-libs/" + libFilename);
File destLib = new File(jettyHomeDir,OS.separators(outputPath));
FS.ensureDirExists(destLib.getParentFile());
IO.copyFile(srcLib,destLib);
}
/**
* Copy the <code>${project.basedir}/src/main/config/</code> tree into the testing directory.
*
* @throws IOException
* if unable to copy the directory tree
*/
public void copyProjectMainConfig() throws IOException
{
File srcDir = MavenTestingUtils.getProjectDir("src/main/config");
IO.copyDir(srcDir,jettyHomeDir);
}
/**
* Create a <code>${jetty.home}/lib/self/${jarFilename}</code> jar file from the content in the <code>${project.basedir}/target/classes/</code> directory.
*
* @throws IOException
* if unable to copy the directory tree
*/
public void createProjectLib(String jarFilename) throws IOException
{
File srcDir = MavenTestingUtils.getTargetFile("classes");
File libSelfDir = new File(jettyHomeDir,OS.separators("lib/self"));
FS.ensureDirExists(libSelfDir);
File jarFile = new File(libSelfDir,jarFilename);
JAR.create(srcDir,jarFile);
}
/**
* Unpack an arbitrary config from <code>target/test-configs/${configFilename}</code> to the testing directory.
*
* @param configFilename
* the <code>target/test-configs/${configFilename}</code> to copy
* @throws IOException
* if unable to unpack config file
*/
public void unpackConfig(String configFilename) throws IOException
{
File srcConfig = MavenTestingUtils.getTargetFile("test-configs/" + configFilename);
JAR.unpack(srcConfig,jettyHomeDir);
}
/**
* Delete a File or Directory found in the ${jetty.home} directory.
*
* @param path
* the path to delete. (can be a file or directory)
*/
public void delete(String path)
{
File jettyPath = new File(jettyHomeDir,OS.separators(path));
FS.delete(jettyPath);
}
/**
* Return the baseUri being used for this Jetty Process Instance.
*
* @return the base URI for this Jetty Process Instance.
*/
public URI getBaseUri()
{
return this.baseUri;
}
/**
* Return the JMX URL being used for this Jetty Process Instance.
*
* @return the JMX URL for this Jetty Process Instance.
*/
public String getJmxUrl()
{
return this.jmxUrl;
}
/**
* Take the directory contents from ${project.basedir}/src/test/resources/${testConfigName}/ and copy it over whatever happens to be at ${jetty.home}
*
* @param testConfigName
* the src/test/resources/ directory name to use as the source diretory for the configuration we are interested in.
* @throws IOException
* if unable to copy directory.
*/
public void overlayConfig(String testConfigName) throws IOException
{
File srcDir = MavenTestingUtils.getTestResourceDir(testConfigName);
IO.copyDir(srcDir,jettyHomeDir);
}
/**
* Start the jetty server
*
* @throws IOException
* if unable to start the server.
*/
public void start() throws IOException
{
List<String> commands = new ArrayList<String>();
commands.add(getJavaBin());
commands.add("-Djetty.home=" + jettyHomeDir.getAbsolutePath());
// Do a dry run first to get the exact command line for Jetty process
commands.add("-jar");
commands.add("start.jar");
commands.add("jetty.port=0");
if (_debug)
{
commands.add("-D.DEBUG=true");
}
commands.add("--dry-run");
ProcessBuilder pbCmd = new ProcessBuilder(commands);
pbCmd.directory(jettyHomeDir);
String cmdLine = null;
Process pidCmd = pbCmd.start();
try
{
cmdLine = readOutputLine(pidCmd);
}
finally
{
pidCmd.destroy();
}
if (cmdLine == null || !cmdLine.contains("XmlConfiguration"))
{
Assert.fail("Unable to get Jetty command line");
}
// Need to breakdown commandline into parts, as spaces in command line will cause failures.
List<String> execCommands = splitAndUnescapeCommandLine(cmdLine);
System.out.printf("Executing: %s%n",cmdLine);
System.out.printf("Working Dir: %s%n",jettyHomeDir.getAbsolutePath());
pbCmd = new ProcessBuilder(execCommands);
pid = pbCmd.start();
ConsoleParser parser = new ConsoleParser();
List<String[]> jmxList = parser.newPattern("JMX Remote URL: (.*)",0);
List<String[]> connList = parser.newPattern("Started [A-Za-z]*Connector@([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)",1);
// DISABLED: This is what exists in Jetty 9+
// List<String[]> connList = parser.newPattern("Started [A-Za-z]*Connector@.*[\\({]([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)[\\)}].*",1);
startPump("STDOUT",parser,this.pid.getInputStream());
startPump("STDERR",parser,this.pid.getErrorStream());
try
{
parser.waitForDone(this.startTime,this.timeUnit);
if (!jmxList.isEmpty())
{
this.jmxUrl = jmxList.get(0)[0];
System.out.printf("## Found JMX connector at %s%n",this.jmxUrl);
}
if (!connList.isEmpty())
{
String[] params = connList.get(0);
if (params.length == 2)
{
this.baseUri = URI.create("http://localhost:" + params[1] + "/");
}
System.out.printf("## Found Jetty connector at host: %s port: %s%n",(Object[])params);
}
}
catch (InterruptedException e)
{
pid.destroy();
Assert.fail("Unable to get required information within time limit");
}
}
public static List<String> splitAndUnescapeCommandLine(CharSequence rawCmdLine)
{
List<String> cmds = new ArrayList<String>();
int len = rawCmdLine.length();
StringBuilder arg = new StringBuilder();
boolean escaped = false;
boolean inQuote = false;
char c;
for (int i = 0; i < len; i++)
{
c = rawCmdLine.charAt(i);
if (escaped)
{
switch (c)
{
case 'r':
arg.append('\r');
break;
case 'f':
arg.append('\f');
break;
case 't':
arg.append('\t');
break;
case 'n':
arg.append('\n');
break;
case 'b':
arg.append('\b');
break;
default:
arg.append(c);
break;
}
escaped = false;
continue;
}
if (c == '\\')
{
escaped = true;
}
else
{
if ((c == ' ') && (!inQuote))
{
// the delim!
cmds.add(String.valueOf(arg.toString()));
arg.setLength(0);
}
else if (c == '"')
{
inQuote = !inQuote;
}
else
{
arg.append(c);
}
}
}
cmds.add(String.valueOf(arg.toString()));
return cmds;
}
private String readOutputLine(Process pidCmd) throws IOException
{
InputStream in = null;
InputStreamReader reader = null;
BufferedReader buf = null;
try
{
in = pidCmd.getInputStream();
reader = new InputStreamReader(in);
buf = new BufferedReader(reader);
return buf.readLine();
}
finally
{
IO.close(buf);
IO.close(reader);
IO.close(in);
}
}
private static class ConsoleParser
{
private List<ConsolePattern> patterns = new ArrayList<ConsolePattern>();
private CountDownLatch latch;
private int count;
public List<String[]> newPattern(String exp, int cnt)
{
ConsolePattern pat = new ConsolePattern(exp,cnt);
patterns.add(pat);
count += cnt;
return pat.getMatches();
}
public void parse(String line)
{
for (ConsolePattern pat : patterns)
{
Matcher mat = pat.getMatcher(line);
if (mat.find())
{
int num = 0, count = mat.groupCount();
String[] match = new String[count];
while (num++ < count)
{
match[num - 1] = mat.group(num);
}
pat.getMatches().add(match);
if (pat.getCount() > 0)
{
getLatch().countDown();
}
}
}
}
public void waitForDone(long timeout, TimeUnit unit) throws InterruptedException
{
getLatch().await(timeout,unit);
}
private CountDownLatch getLatch()
{
synchronized (this)
{
if (latch == null)
{
latch = new CountDownLatch(count);
}
}
return latch;
}
}
private static class ConsolePattern
{
private Pattern pattern;
private List<String[]> matches;
private int count;
ConsolePattern(String exp, int cnt)
{
pattern = Pattern.compile(exp);
matches = new ArrayList<String[]>();
count = cnt;
}
public Matcher getMatcher(String line)
{
return pattern.matcher(line);
}
public List<String[]> getMatches()
{
return matches;
}
public int getCount()
{
return count;
}
}
private void startPump(String mode, ConsoleParser parser, InputStream inputStream)
{
ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
pump.setParser(parser);
Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
thread.start();
}
/**
* enable debug on the jetty process
*
* @param debug
*/
public void setDebug(boolean debug)
{
_debug = debug;
}
private String getJavaBin()
{
String javaexes[] = new String[]
{ "java", "java.exe" };
File javaHomeDir = new File(System.getProperty("java.home"));
for (String javaexe : javaexes)
{
File javabin = new File(javaHomeDir,OS.separators("bin/" + javaexe));
if (javabin.exists() && javabin.isFile())
{
return javabin.getAbsolutePath();
}
}
Assert.fail("Unable to find java bin");
return "java";
}
/**
* Stop the jetty server
*/
public void stop()
{
System.out.println("Stopping JettyDistro ...");
if (pid != null)
{
// TODO: maybe issue a STOP instead?
pid.destroy();
}
}
/**
* Simple streamer for the console output from a Process
*/
private static class ConsoleStreamer implements Runnable
{
private String mode;
private BufferedReader reader;
private ConsoleParser parser;
public ConsoleStreamer(String mode, InputStream is)
{
this.mode = mode;
this.reader = new BufferedReader(new InputStreamReader(is));
}
public void setParser(ConsoleParser connector)
{
this.parser = connector;
}
public void run()
{
String line;
// System.out.printf("ConsoleStreamer/%s initiated%n",mode);
try
{
while ((line = reader.readLine()) != (null))
{
if (parser != null)
{
parser.parse(line);
}
System.out.println("[" + mode + "] " + line);
}
}
catch (IOException ignore)
{
/* ignore */
}
finally
{
IO.close(reader);
}
// System.out.printf("ConsoleStreamer/%s finished%n",mode);
}
}
public void setStartTime(long startTime, TimeUnit timeUnit)
{
this.startTime = startTime;
this.timeUnit = timeUnit;
}
}