Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Lachlan Roberts 2020-10-16 15:21:25 +11:00
commit 08cf431e88
11 changed files with 240 additions and 171 deletions

View File

@ -62,6 +62,7 @@ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotat
for (String r : roles) for (String r : roles)
{ {
((ConstraintSecurityHandler)_context.getSecurityHandler()).addRole(r); ((ConstraintSecurityHandler)_context.getSecurityHandler()).addRole(r);
_context.getMetaData().setOrigin("security-role." + r, declareRoles, clazz);
} }
} }
} }

View File

@ -108,7 +108,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
FilterMapping mapping = new FilterMapping(); FilterMapping mapping = new FilterMapping();
mapping.setFilterName(holder.getName()); mapping.setFilterName(holder.getName());
metaData.setOrigin(name + ".filter.mapping." + Long.toHexString(mapping.hashCode()), filterAnnotation, clazz);
if (urlPatterns.length > 0) if (urlPatterns.length > 0)
{ {
ArrayList<String> paths = new ArrayList<String>(); ArrayList<String> paths = new ArrayList<String>();
@ -179,7 +179,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
{ {
FilterMapping mapping = new FilterMapping(); FilterMapping mapping = new FilterMapping();
mapping.setFilterName(holder.getName()); mapping.setFilterName(holder.getName());
metaData.setOrigin(holder.getName() + ".filter.mapping." + Long.toHexString(mapping.hashCode()), filterAnnotation, clazz);
if (urlPatterns.length > 0) if (urlPatterns.length > 0)
{ {
ArrayList<String> paths = new ArrayList<String>(); ArrayList<String> paths = new ArrayList<String>();

View File

@ -154,6 +154,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
mapping = new ServletMapping(source); mapping = new ServletMapping(source);
mapping.setServletName(holder.getName()); mapping.setServletName(holder.getName());
mapping.setPathSpecs(LazyList.toStringArray(urlPatternList)); mapping.setPathSpecs(LazyList.toStringArray(urlPatternList));
_context.getMetaData().setOrigin(servletName + ".servlet.mapping." + Long.toHexString(mapping.hashCode()), annotation, clazz);
} }
else else
{ {
@ -190,6 +191,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
mapping = new ServletMapping(new Source(Source.Origin.ANNOTATION, clazz.getName())); mapping = new ServletMapping(new Source(Source.Origin.ANNOTATION, clazz.getName()));
mapping.setServletName(servletName); mapping.setServletName(servletName);
mapping.setPathSpecs(LazyList.toStringArray(urlPatternList)); mapping.setPathSpecs(LazyList.toStringArray(urlPatternList));
_context.getMetaData().setOrigin(servletName + ".servlet.mapping." + Long.toHexString(mapping.hashCode()), annotation, clazz);
} }
} }
@ -228,7 +230,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
LOG.debug("Removed path {} from mapping {} from defaults descriptor ", p, existingMapping); LOG.debug("Removed path {} from mapping {} from defaults descriptor ", p, existingMapping);
} }
} }
_context.getMetaData().setOrigin(servletName + ".servlet.mapping." + p, annotation, clazz); _context.getMetaData().setOrigin(servletName + ".servlet.mapping.url" + p, annotation, clazz);
} }
allMappings.add(mapping); allMappings.add(mapping);
_context.getServletHandler().setServletMappings(allMappings.toArray(new ServletMapping[allMappings.size()])); _context.getServletHandler().setServletMappings(allMappings.toArray(new ServletMapping[allMappings.size()]));

View File

@ -304,11 +304,11 @@ public class HttpExchange
{ {
try (AutoLock l = lock.lock()) try (AutoLock l = lock.lock())
{ {
return String.format("%s@%x req=%s/%s@%h res=%s/%s@%h", return String.format("%s@%x{req=%s[%s/%s] res=%s[%s/%s]}",
HttpExchange.class.getSimpleName(), HttpExchange.class.getSimpleName(),
hashCode(), hashCode(),
requestState, requestFailure, requestFailure, request, requestState, requestFailure,
responseState, responseFailure, responseFailure); response, responseState, responseFailure);
} }
} }

View File

@ -189,7 +189,7 @@ public abstract class HttpReceiver
{ {
handlerListener = protocolHandler.getResponseListener(); handlerListener = protocolHandler.getResponseListener();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Found protocol handler {}", protocolHandler); LOG.debug("Response {} found protocol handler {}", response, protocolHandler);
} }
exchange.getConversation().updateResponseListeners(handlerListener); exchange.getConversation().updateResponseListeners(handlerListener);
@ -220,19 +220,8 @@ public abstract class HttpReceiver
*/ */
protected boolean responseHeader(HttpExchange exchange, HttpField field) protected boolean responseHeader(HttpExchange exchange, HttpField field)
{ {
while (true) if (!updateResponseState(ResponseState.BEGIN, ResponseState.HEADER, ResponseState.TRANSIENT))
{ return false;
ResponseState current = responseState.get();
if (current == ResponseState.BEGIN || current == ResponseState.HEADER)
{
if (updateResponseState(current, ResponseState.TRANSIENT))
break;
}
else
{
return false;
}
}
HttpResponse response = exchange.getResponse(); HttpResponse response = exchange.getResponse();
ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
@ -298,19 +287,8 @@ public abstract class HttpReceiver
*/ */
protected boolean responseHeaders(HttpExchange exchange) protected boolean responseHeaders(HttpExchange exchange)
{ {
while (true) if (!updateResponseState(ResponseState.BEGIN, ResponseState.HEADER, ResponseState.TRANSIENT))
{ return false;
ResponseState current = responseState.get();
if (current == ResponseState.BEGIN || current == ResponseState.HEADER)
{
if (updateResponseState(current, ResponseState.TRANSIENT))
break;
}
else
{
return false;
}
}
HttpResponse response = exchange.getResponse(); HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
@ -344,7 +322,7 @@ public abstract class HttpReceiver
{ {
boolean hasDemand = hasDemandOrStall(); boolean hasDemand = hasDemandOrStall();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Response headers {}, hasDemand={}", response, hasDemand); LOG.debug("Response headers hasDemand={} {}", hasDemand, response);
return hasDemand; return hasDemand;
} }
@ -365,71 +343,39 @@ public abstract class HttpReceiver
*/ */
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback) protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback)
{ {
while (true) if (LOG.isDebugEnabled())
{ LOG.debug("Response content {}{}{}", exchange.getResponse(), System.lineSeparator(), BufferUtil.toDetailString(buffer));
ResponseState current = responseState.get();
if (current == ResponseState.HEADERS || current == ResponseState.CONTENT)
{
if (updateResponseState(current, ResponseState.TRANSIENT))
break;
}
else
{
callback.failed(new IllegalStateException("Invalid response state " + current));
return false;
}
}
boolean proceed = true;
if (demand() <= 0) if (demand() <= 0)
{ {
callback.failed(new IllegalStateException("No demand for response content")); callback.failed(new IllegalStateException("No demand for response content"));
proceed = false; return false;
}
if (decoder == null)
return plainResponseContent(exchange, buffer, callback);
else
return decodeResponseContent(buffer, callback);
}
private boolean plainResponseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback)
{
if (!updateResponseState(ResponseState.HEADERS, ResponseState.CONTENT, ResponseState.TRANSIENT))
{
callback.failed(new IllegalStateException("Invalid response state " + responseState));
return false;
} }
HttpResponse response = exchange.getResponse(); HttpResponse response = exchange.getResponse();
if (proceed) if (contentListeners.isEmpty())
{ callback.succeeded();
if (LOG.isDebugEnabled()) else
LOG.debug("Response content {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(buffer)); contentListeners.notifyContent(response, buffer, callback);
if (contentListeners.isEmpty())
{
callback.succeeded();
}
else
{
if (decoder == null)
{
contentListeners.notifyContent(response, buffer, callback);
}
else
{
try
{
proceed = decoder.decode(buffer, callback);
}
catch (Throwable x)
{
callback.failed(x);
proceed = false;
}
}
}
}
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT)) if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT))
{ {
if (proceed) boolean hasDemand = hasDemandOrStall();
{ if (LOG.isDebugEnabled())
boolean hasDemand = hasDemandOrStall(); LOG.debug("Response content {}, hasDemand={}", response, hasDemand);
if (LOG.isDebugEnabled()) return hasDemand;
LOG.debug("Response content {}, hasDemand={}", response, hasDemand);
return hasDemand;
}
else
{
return false;
}
} }
dispose(); dispose();
@ -437,6 +383,11 @@ public abstract class HttpReceiver
return false; return false;
} }
private boolean decodeResponseContent(ByteBuffer buffer, Callback callback)
{
return decoder.decode(buffer, callback);
}
/** /**
* Method to be invoked when the response is successful. * Method to be invoked when the response is successful.
* <p> * <p>
@ -616,15 +567,42 @@ public abstract class HttpReceiver
} }
} }
private boolean updateResponseState(ResponseState from1, ResponseState from2, ResponseState to)
{
while (true)
{
ResponseState current = responseState.get();
if (current == from1 || current == from2)
{
if (updateResponseState(current, to))
return true;
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("State update failed: [{},{}] -> {}: {}", from1, from2, to, current);
return false;
}
}
}
private boolean updateResponseState(ResponseState from, ResponseState to) private boolean updateResponseState(ResponseState from, ResponseState to)
{ {
boolean updated = responseState.compareAndSet(from, to); while (true)
if (!updated)
{ {
if (LOG.isDebugEnabled()) ResponseState current = responseState.get();
LOG.debug("State update failed: {} -> {}: {}", from, to, responseState.get()); if (current == from)
{
if (responseState.compareAndSet(current, to))
return true;
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("State update failed: {} -> {}: {}", from, to, current);
return false;
}
} }
return updated;
} }
@Override @Override
@ -780,14 +758,62 @@ public abstract class HttpReceiver
private boolean decode(ByteBuffer encoded, Callback callback) private boolean decode(ByteBuffer encoded, Callback callback)
{ {
// Store the buffer to decode in case the
// decoding produces multiple decoded buffers.
this.encoded = encoded; this.encoded = encoded;
this.callback = callback; this.callback = callback;
return decode();
HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled())
LOG.debug("Response content decoding {} with {}{}{}", response, decoder, System.lineSeparator(), BufferUtil.toDetailString(encoded));
boolean needInput = decode();
if (!needInput)
return false;
boolean hasDemand = hasDemandOrStall();
if (LOG.isDebugEnabled())
LOG.debug("Response content decoded, hasDemand={} {}", hasDemand, response);
return hasDemand;
} }
private boolean decode() private boolean decode()
{ {
while (true) while (true)
{
if (!updateResponseState(ResponseState.HEADERS, ResponseState.CONTENT, ResponseState.TRANSIENT))
{
callback.failed(new IllegalStateException("Invalid response state " + responseState));
return false;
}
DecodeResult result = decodeChunk();
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT))
{
if (result == DecodeResult.NEED_INPUT)
return true;
if (result == DecodeResult.ABORT)
return false;
boolean hasDemand = hasDemandOrStall();
if (LOG.isDebugEnabled())
LOG.debug("Response content decoded chunk, hasDemand={} {}", hasDemand, exchange.getResponse());
if (hasDemand)
continue;
else
return false;
}
dispose();
terminateResponse(exchange);
return false;
}
}
private DecodeResult decodeChunk()
{
try
{ {
ByteBuffer buffer; ByteBuffer buffer;
while (true) while (true)
@ -800,27 +826,30 @@ public abstract class HttpReceiver
callback.succeeded(); callback.succeeded();
encoded = null; encoded = null;
callback = null; callback = null;
return true; return DecodeResult.NEED_INPUT;
} }
} }
ByteBuffer decoded = buffer; ByteBuffer decoded = buffer;
HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Response content decoded ({}) {}{}{}", decoder, exchange, System.lineSeparator(), BufferUtil.toDetailString(decoded)); LOG.debug("Response content decoded chunk {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(decoded));
contentListeners.notifyContent(exchange.getResponse(), decoded, Callback.from(() -> decoder.release(decoded), callback::failed)); contentListeners.notifyContent(response, decoded, Callback.from(() -> decoder.release(decoded), callback::failed));
boolean hasDemand = hasDemandOrStall(); return DecodeResult.DECODE;
if (LOG.isDebugEnabled()) }
LOG.debug("Response content decoded {}, hasDemand={}", exchange, hasDemand); catch (Throwable x)
if (!hasDemand) {
return false; callback.failed(x);
return DecodeResult.ABORT;
} }
} }
private void resume() private void resume()
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Response content resuming decoding {}", exchange); LOG.debug("Response content resume decoding {} with {}", exchange.getResponse(), decoder);
// The content and callback may be null // The content and callback may be null
// if there is no initial content demand. // if there is no initial content demand.
@ -830,40 +859,9 @@ public abstract class HttpReceiver
return; return;
} }
while (true) boolean needInput = decode();
{ if (needInput)
ResponseState current = responseState.get(); receive();
if (current == ResponseState.HEADERS || current == ResponseState.CONTENT)
{
if (updateResponseState(current, ResponseState.TRANSIENT))
break;
}
else
{
callback.failed(new IllegalStateException("Invalid response state " + current));
return;
}
}
boolean decoded = false;
try
{
decoded = decode();
}
catch (Throwable x)
{
callback.failed(x);
}
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT))
{
if (decoded)
receive();
return;
}
dispose();
terminateResponse(exchange);
} }
@Override @Override
@ -873,4 +871,9 @@ public abstract class HttpReceiver
((Destroyable)decoder).destroy(); ((Destroyable)decoder).destroy();
} }
} }
private enum DecodeResult
{
DECODE, NEED_INPUT, ABORT
}
} }

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
@ -32,11 +33,14 @@ import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ArgumentsSource;
@ -266,6 +270,47 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest
assertThat(heapMemory, lessThan((long)content.length)); assertThat(heapMemory, lessThan((long)content.length));
} }
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testLargeGZIPContentAsync(Scenario scenario) throws Exception
{
String digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
Random random = new Random();
byte[] content = new byte[32 * 1024 * 1024];
for (int i = 0; i < content.length; ++i)
{
content[i] = (byte)digits.charAt(random.nextInt(digits.length()));
}
start(scenario, new EmptyServerHandler()
{
@Override
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.setContentType("text/plain;charset=" + StandardCharsets.US_ASCII.name());
response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream());
gzip.write(content);
gzip.finish();
}
});
InputStreamResponseListener listener = new InputStreamResponseListener();
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.timeout(5, TimeUnit.SECONDS)
.send(listener);
Response response = listener.get(5, TimeUnit.SECONDS);
assertEquals(HttpStatus.OK_200, response.getStatus());
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (InputStream input = listener.getInputStream())
{
IO.copy(input, output);
}
assertArrayEquals(content, output.toByteArray());
}
private static void sleep(long ms) throws IOException private static void sleep(long ms) throws IOException
{ {
try try

View File

@ -20,7 +20,7 @@
==== Configuring JNDI Entries ==== Configuring JNDI Entries
A web application may _reference_ a JNDI entry, such as a JDBC `DataSource` from the web application `web.xml` file. A web application may _reference_ a JNDI entry, such as a JDBC `DataSource` from the web application `web.xml` file.
The JNDI entry must be _defined_ in the Jetty context XML file, for example: The JNDI entry must be _defined_ in a link:#og-jndi-xml[Jetty XML file], for example a context XML like so:
.mywebapp.xml .mywebapp.xml
[source,xml,subs=normal] [source,xml,subs=normal]
@ -28,11 +28,11 @@ The JNDI entry must be _defined_ in the Jetty context XML file, for example:
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd"> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Configure id="wac" class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/mywebapp</Set> <Set name="contextPath">/mywebapp</Set>
<Set name="war">/opt/webapps/mywebapp.war</Set> <Set name="war">/opt/webapps/mywebapp.war</Set>
#&nbsp;&nbsp;<New class="org.eclipse.jetty.plus.jndi.Resource"> #&nbsp;&nbsp;<New class="org.eclipse.jetty.plus.jndi.Resource">
<Arg /> <Arg><Ref refid="wac"/></Arg>
<Arg>jdbc/myds</Arg> <Arg>jdbc/myds</Arg>
<Arg> <Arg>
<New class="com.mysql.cj.jdbc.MysqlConnectionPoolDataSource"> <New class="com.mysql.cj.jdbc.MysqlConnectionPoolDataSource">
@ -45,11 +45,14 @@ The JNDI entry must be _defined_ in the Jetty context XML file, for example:
</Configure> </Configure>
---- ----
For more information and examples on how to use JNDI in Jetty, refer to the link:#og-jndi[JNDI] feature section.
[IMPORTANT] [IMPORTANT]
==== ====
Class `com.mysql.cj.jdbc.MysqlConnectionPoolDataSource` is present in the MySQL JDBC driver file, `mysql-connector-java-<version>.jar`, which must be available to the web application. Class `com.mysql.cj.jdbc.MysqlConnectionPoolDataSource` is present in the MySQL JDBC driver file, `mysql-connector-java-<version>.jar`, which must be available on the server's classpath .
If the class is instead present _within_ the web application, then the JNDI entry must be declared in a `WEB-INF/jetty-env.xml` file - see the link:#og-jndi[JNDI] feature section for more information and examples.
File `mysql-connector-java-<version>.jar` must be either present in `WEB-INF/lib`, or in the Jetty server class-path.
==== ====
// TODO: add a link to reference the section about how // TODO: add a link to reference the section about how
// to add a JDBC driver jar to the server classpath. // to add a JDBC driver jar to the server classpath.

View File

@ -329,14 +329,13 @@ Server XML file::
Naming resources defined in a server XML file are link:#og-jndi-scope[scoped] at either the JVM level or the `org.eclipse.jetty.server.Server` level. Naming resources defined in a server XML file are link:#og-jndi-scope[scoped] at either the JVM level or the `org.eclipse.jetty.server.Server` level.
The classes for the resource _must_ be visible at the Jetty *container* level. The classes for the resource _must_ be visible at the Jetty *container* level.
If instead the classes for the resource only exist inside your webapp, you must declare it in a `WEB-INF/jetty-env.xml` file. If instead the classes for the resource only exist inside your webapp, you must declare it in a `WEB-INF/jetty-env.xml` file.
Context XML file::
Entries in a context XML file should be link:#og-jndi-scope[scoped] at the level of the webapp to which they apply (although it is possible to use a less strict scoping level of Server or JVM, but not recommended).
As with resources declared in a server XML file, classes associated with the resource _must_ be visible on the *container's* classpath.
WEB-INF/jetty-env.xml:: WEB-INF/jetty-env.xml::
Naming resources in a `WEB-INF/jetty-env.xml` file are link:#og-jndi-scope[scoped] to the webapp in which the file resides. Naming resources in a `WEB-INF/jetty-env.xml` file are link:#og-jndi-scope[scoped] to the webapp in which the file resides.
While you can enter JVM or Server scopes if you choose, we do not recommend doing so. While you can enter JVM or Server scopes if you choose, we do not recommend doing so.
The resources defined here may use classes from inside your webapp. The resources defined here may use classes from inside your webapp.
This is a Jetty-specific mechanism.
Context XML file::
Entries in a context XML file should be link:#og-jndi-scope[scoped] at the level of the webapp to which they apply (although it is possible to use a less strict scoping level of Server or JVM, but not recommended).
As with resources declared in a server XML file, classes associated with the resource _must_ be visible on the *container's* classpath.
[[og-jndi-scope]] [[og-jndi-scope]]
===== Resource scoping ===== Resource scoping

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.quickstart; package org.eclipse.jetty.quickstart;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.webapp.Descriptor; import org.eclipse.jetty.webapp.Descriptor;
import org.eclipse.jetty.webapp.IterativeDescriptorProcessor; import org.eclipse.jetty.webapp.IterativeDescriptorProcessor;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
@ -36,12 +37,11 @@ public class ExtraXmlDescriptorProcessor extends IterativeDescriptorProcessor
private static final Logger LOG = LoggerFactory.getLogger(ExtraXmlDescriptorProcessor.class); private static final Logger LOG = LoggerFactory.getLogger(ExtraXmlDescriptorProcessor.class);
private final StringBuilder _buffer = new StringBuilder(); private final StringBuilder _buffer = new StringBuilder();
private final boolean _showOrigin; private String _originAttribute;
private String _origin; private String _origin;
public ExtraXmlDescriptorProcessor() public ExtraXmlDescriptorProcessor()
{ {
_showOrigin = LOG.isDebugEnabled();
try try
{ {
registerVisitor("env-entry", getClass().getMethod("saveSnippet", __signature)); registerVisitor("env-entry", getClass().getMethod("saveSnippet", __signature));
@ -60,7 +60,7 @@ public class ExtraXmlDescriptorProcessor extends IterativeDescriptorProcessor
public void start(WebAppContext context, Descriptor descriptor) public void start(WebAppContext context, Descriptor descriptor)
{ {
LOG.debug("process {}", descriptor); LOG.debug("process {}", descriptor);
_origin = (" <!-- " + descriptor + " -->\n"); _origin = (StringUtil.isBlank(_originAttribute) ? null : " <!-- " + descriptor + " -->\n");
} }
@Override @Override
@ -68,11 +68,19 @@ public class ExtraXmlDescriptorProcessor extends IterativeDescriptorProcessor
{ {
} }
public void setOriginAttribute(String name)
{
_originAttribute = name;
}
public void saveSnippet(WebAppContext context, Descriptor descriptor, XmlParser.Node node) public void saveSnippet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
throws Exception throws Exception
{ {
//Note: we have to output the origin as a comment field instead of
//as an attribute like the other other elements because
//we are copying these elements _verbatim_ from the descriptor
LOG.debug("save {}", node.getTag()); LOG.debug("save {}", node.getTag());
if (_showOrigin) if (_origin != null)
_buffer.append(_origin); _buffer.append(_origin);
_buffer.append(" ").append(node.toString()).append("\n"); _buffer.append(" ").append(node.toString()).append("\n");
} }

View File

@ -241,7 +241,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
if (f != null && f.getSource() == Source.EMBEDDED) if (f != null && f.getSource() == Source.EMBEDDED)
continue; continue;
out.openTag("filter-mapping"); out.openTag("filter-mapping", origin(md, mapping.getFilterName() + ".filter.mapping." + Long.toHexString(mapping.hashCode())));
out.tag("filter-name", mapping.getFilterName()); out.tag("filter-name", mapping.getFilterName());
if (mapping.getPathSpecs() != null) if (mapping.getPathSpecs() != null)
for (String s : mapping.getPathSpecs()) for (String s : mapping.getPathSpecs())
@ -289,7 +289,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
if (sh != null && sh.getSource() == Source.EMBEDDED) if (sh != null && sh.getSource() == Source.EMBEDDED)
continue; continue;
out.openTag("servlet-mapping", origin(md, mapping.getServletName() + ".servlet.mappings")); out.openTag("servlet-mapping", origin(md, mapping.getServletName() + ".servlet.mapping." + Long.toHexString(mapping.hashCode())));
out.tag("servlet-name", mapping.getServletName()); out.tag("servlet-name", mapping.getServletName());
if (mapping.getPathSpecs() != null) if (mapping.getPathSpecs() != null)
for (String s : mapping.getPathSpecs()) for (String s : mapping.getPathSpecs())
@ -327,7 +327,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
ConstraintAware ca = (ConstraintAware)security; ConstraintAware ca = (ConstraintAware)security;
for (String r : ca.getRoles()) for (String r : ca.getRoles())
{ {
out.openTag("security-role") out.openTag("security-role", origin(md, "security-role." + r))
.tag("role-name", r) .tag("role-name", r)
.closeTag(); .closeTag();
} }
@ -398,7 +398,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
out.openTag("welcome-file-list"); out.openTag("welcome-file-list");
for (String welcomeFile : context.getWelcomeFiles()) for (String welcomeFile : context.getWelcomeFiles())
{ {
out.tag("welcome-file", welcomeFile); out.tag("welcome-file", origin(md, "welcome-file." + welcomeFile), welcomeFile);
} }
out.closeTag(); out.closeTag();
} }
@ -429,6 +429,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
if (cookieConfig != null) if (cookieConfig != null)
{ {
out.openTag("cookie-config"); out.openTag("cookie-config");
if (cookieConfig.getName() != null) if (cookieConfig.getName() != null)
out.tag("name", origin(md, "cookie-config.name"), cookieConfig.getName()); out.tag("name", origin(md, "cookie-config.name"), cookieConfig.getName());
@ -789,6 +790,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
public void preConfigure(WebAppContext context) throws Exception public void preConfigure(WebAppContext context) throws Exception
{ {
ExtraXmlDescriptorProcessor extraXmlProcessor = new ExtraXmlDescriptorProcessor(); ExtraXmlDescriptorProcessor extraXmlProcessor = new ExtraXmlDescriptorProcessor();
extraXmlProcessor.setOriginAttribute(getOriginAttribute());
context.getMetaData().addDescriptorProcessor(extraXmlProcessor); context.getMetaData().addDescriptorProcessor(extraXmlProcessor);
context.setAttribute(ExtraXmlDescriptorProcessor.class.getName(), extraXmlProcessor); context.setAttribute(ExtraXmlDescriptorProcessor.class.getName(), extraXmlProcessor);
super.preConfigure(context); super.preConfigure(context);

View File

@ -663,6 +663,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
if (TimeUnit.MINUTES.toSeconds(mins) > Integer.MAX_VALUE) if (TimeUnit.MINUTES.toSeconds(mins) > Integer.MAX_VALUE)
throw new IllegalStateException("Max session-timeout in minutes is " + TimeUnit.SECONDS.toMinutes(Integer.MAX_VALUE)); throw new IllegalStateException("Max session-timeout in minutes is " + TimeUnit.SECONDS.toMinutes(Integer.MAX_VALUE));
context.getServletContext().setSessionTimeout((int)mins); context.getServletContext().setSessionTimeout((int)mins);
context.getMetaData().setOrigin("session.timeout", descriptor);
} }
//Servlet Spec 3.0 //Servlet Spec 3.0
@ -672,7 +673,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
if (iter.hasNext()) if (iter.hasNext())
{ {
Set<SessionTrackingMode> modes = null; Set<SessionTrackingMode> modes = null;
Origin o = context.getMetaData().getOrigin("session.tracking-mode"); Origin o = context.getMetaData().getOrigin("session.tracking-modes");
switch (o) switch (o)
{ {
case NotSet://not previously set, starting fresh case NotSet://not previously set, starting fresh
@ -680,7 +681,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
modes = new HashSet<SessionTrackingMode>(); modes = new HashSet<SessionTrackingMode>();
context.getMetaData().setOrigin("session.tracking-mode", descriptor); context.getMetaData().setOrigin("session.tracking-modes", descriptor);
break; break;
} }
case WebXml: case WebXml:
@ -692,7 +693,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
modes = new HashSet<SessionTrackingMode>(); modes = new HashSet<SessionTrackingMode>();
else else
modes = new HashSet<SessionTrackingMode>(context.getSessionHandler().getEffectiveSessionTrackingModes()); modes = new HashSet<SessionTrackingMode>(context.getSessionHandler().getEffectiveSessionTrackingModes());
context.getMetaData().setOrigin("session.tracking-mode", descriptor); context.getMetaData().setOrigin("session.tracking-modes", descriptor);
break; break;
} }
default: default:
@ -703,7 +704,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
XmlParser.Node mNode = (XmlParser.Node)iter.next(); XmlParser.Node mNode = (XmlParser.Node)iter.next();
String trackMode = mNode.toString(false, true); String trackMode = mNode.toString(false, true);
modes.add(SessionTrackingMode.valueOf(trackMode)); SessionTrackingMode mode = SessionTrackingMode.valueOf(trackMode);
modes.add(mode);
context.getMetaData().setOrigin("session.tracking-mode." + mode, descriptor);
} }
context.getSessionHandler().setSessionTrackingModes(modes); context.getSessionHandler().setSessionTrackingModes(modes);
} }
@ -1035,13 +1038,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
case NotSet: case NotSet:
{ {
context.getMetaData().setOrigin("welcome-file-list", descriptor); context.getMetaData().setOrigin("welcome-file-list", descriptor);
addWelcomeFiles(context, node); addWelcomeFiles(context, node, descriptor);
break; break;
} }
case WebXml: case WebXml:
{ {
//web.xml set the welcome-file-list, all other descriptors then just merge in //web.xml set the welcome-file-list, all other descriptors then just merge in
addWelcomeFiles(context, node); addWelcomeFiles(context, node, descriptor);
break; break;
} }
case WebDefaults: case WebDefaults:
@ -1052,19 +1055,19 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
context.setWelcomeFiles(new String[0]); context.setWelcomeFiles(new String[0]);
} }
addWelcomeFiles(context, node); addWelcomeFiles(context, node, descriptor);
break; break;
} }
case WebOverride: case WebOverride:
{ {
//web-override set the list, all other descriptors just merge in //web-override set the list, all other descriptors just merge in
addWelcomeFiles(context, node); addWelcomeFiles(context, node, descriptor);
break; break;
} }
case WebFragment: case WebFragment:
{ {
//A web-fragment first set the welcome-file-list. Other descriptors just add. //A web-fragment first set the welcome-file-list. Other descriptors just add.
addWelcomeFiles(context, node); addWelcomeFiles(context, node, descriptor);
break; break;
} }
default: default:
@ -1182,14 +1185,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
} }
} }
public void addWelcomeFiles(WebAppContext context, XmlParser.Node node) public void addWelcomeFiles(WebAppContext context, XmlParser.Node node, Descriptor descriptor)
{ {
Iterator<XmlParser.Node> iter = node.iterator("welcome-file"); Iterator<XmlParser.Node> iter = node.iterator("welcome-file");
while (iter.hasNext()) while (iter.hasNext())
{ {
XmlParser.Node indexNode = (XmlParser.Node)iter.next(); XmlParser.Node indexNode = (XmlParser.Node)iter.next();
String welcome = indexNode.toString(false, true); String welcome = indexNode.toString(false, true);
context.getMetaData().setOrigin("welcome-file." + welcome, descriptor);
//Servlet Spec 3.0 p. 74 welcome files are additive //Servlet Spec 3.0 p. 74 welcome files are additive
if (welcome != null && welcome.trim().length() > 0) if (welcome != null && welcome.trim().length() > 0)
context.setWelcomeFiles((String[])ArrayUtil.addToArray(context.getWelcomeFiles(), welcome, String.class)); context.setWelcomeFiles((String[])ArrayUtil.addToArray(context.getWelcomeFiles(), welcome, String.class));
@ -1201,7 +1204,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
ServletMapping mapping = new ServletMapping(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString())); ServletMapping mapping = new ServletMapping(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
mapping.setServletName(servletName); mapping.setServletName(servletName);
mapping.setFromDefaultDescriptor(descriptor instanceof DefaultsDescriptor); mapping.setFromDefaultDescriptor(descriptor instanceof DefaultsDescriptor);
context.getMetaData().setOrigin(servletName + ".servlet.mapping." + Long.toHexString(mapping.hashCode()), descriptor);
List<String> paths = new ArrayList<String>(); List<String> paths = new ArrayList<String>();
Iterator<XmlParser.Node> iter = node.iterator("url-pattern"); Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
while (iter.hasNext()) while (iter.hasNext())
@ -1255,7 +1259,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
} }
paths.add(p); paths.add(p);
context.getMetaData().setOrigin(servletName + ".servlet.mapping." + p, descriptor); context.getMetaData().setOrigin(servletName + ".servlet.mapping.url" + p, descriptor);
} }
mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()])); mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
@ -1269,7 +1273,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
FilterMapping mapping = new FilterMapping(); FilterMapping mapping = new FilterMapping();
mapping.setFilterName(filterName); mapping.setFilterName(filterName);
context.getMetaData().setOrigin(filterName + ".filter.mapping." + Long.toHexString(mapping.hashCode()), descriptor);
List<String> paths = new ArrayList<String>(); List<String> paths = new ArrayList<String>();
Iterator<XmlParser.Node> iter = node.iterator("url-pattern"); Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
while (iter.hasNext()) while (iter.hasNext())
@ -1277,7 +1281,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
String p = iter.next().toString(false, true); String p = iter.next().toString(false, true);
p = ServletPathSpec.normalize(p); p = ServletPathSpec.normalize(p);
paths.add(p); paths.add(p);
context.getMetaData().setOrigin(filterName + ".filter.mapping." + p, descriptor); context.getMetaData().setOrigin(filterName + ".filter.mapping.url" + p, descriptor);
} }
mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()])); mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
@ -1286,6 +1290,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
while (iter.hasNext()) while (iter.hasNext())
{ {
String n = ((XmlParser.Node)iter.next()).toString(false, true); String n = ((XmlParser.Node)iter.next()).toString(false, true);
context.getMetaData().setOrigin(filterName + ".filter.mapping.servlet" + n, descriptor);
names.add(n); names.add(n);
} }
mapping.setServletNames((String[])names.toArray(new String[names.size()])); mapping.setServletNames((String[])names.toArray(new String[names.size()]));
@ -1741,6 +1746,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
XmlParser.Node roleNode = node.get("role-name"); XmlParser.Node roleNode = node.get("role-name");
String role = roleNode.toString(false, true); String role = roleNode.toString(false, true);
((ConstraintAware)context.getSecurityHandler()).addRole(role); ((ConstraintAware)context.getSecurityHandler()).addRole(role);
context.getMetaData().setOrigin("security-role." + role, descriptor);
} }
public void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node) public void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)