Skip to content

Commit

Permalink
Issue #5032 - WebAppContext & Configuration hooks added
Browse files Browse the repository at this point in the history
Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
  • Loading branch information
joakime committed Sep 15, 2020
1 parent 5c2dcc6 commit eced4cc
Show file tree
Hide file tree
Showing 11 changed files with 424 additions and 9 deletions.
6 changes: 6 additions & 0 deletions jetty-metrics/pom.xml
Expand Up @@ -58,5 +58,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,75 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.metrics;

import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;

public class MetricsConfigurationWrapper extends Configuration.Wrapper
{
private final WebAppMetricsListener metricsListener;

public MetricsConfigurationWrapper(Configuration configuration, WebAppMetricsListener metricsListener)
{
super(configuration);
this.metricsListener = metricsListener;
}

@Override
public void preConfigure(WebAppContext context) throws Exception
{
try
{
metricsListener.onWebAppConfigureStart(context, getWrapped(), WebAppMetricsListener.ConfigurationStep.PRE);
super.preConfigure(context);
}
finally
{
metricsListener.onWebAppConfigureFinished(context, getWrapped(), WebAppMetricsListener.ConfigurationStep.PRE);
}
}

@Override
public void configure(WebAppContext context) throws Exception
{
try
{
metricsListener.onWebAppConfigureStart(context, getWrapped(), WebAppMetricsListener.ConfigurationStep.MAIN);
super.configure(context);
}
finally
{
metricsListener.onWebAppConfigureFinished(context, getWrapped(), WebAppMetricsListener.ConfigurationStep.MAIN);
}
}

@Override
public void postConfigure(WebAppContext context) throws Exception
{
try
{
metricsListener.onWebAppConfigureStart(context, getWrapped(), WebAppMetricsListener.ConfigurationStep.POST);
super.postConfigure(context);
}
finally
{
metricsListener.onWebAppConfigureFinished(context, getWrapped(), WebAppMetricsListener.ConfigurationStep.POST);
}
}
}
Expand Up @@ -36,12 +36,14 @@
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;

public class MetricsHandler extends ContainerLifeCycle
implements ServletHolder.WrapperFunction,
FilterHolder.WrapperFunction,
ListenerHolder.WrapperFunction,
Configuration.WrapperFunction,
HttpChannel.Listener,
LifeCycle.Listener
{
Expand Down Expand Up @@ -129,6 +131,31 @@ public void onRequestBegin(Request request)
request.setAttribute(ATTR_REQUEST_ID, uniqId);
}

@Override
public Configuration wrapConfiguration(Configuration configuration)
{
LOG.info("wrapConfiguration({})", configuration);
if (!(metricsListener instanceof WebAppMetricsListener))
{
return configuration;
}

Configuration unwrapped = configuration;
while (unwrapped instanceof Configuration.Wrapper)
{
// Are we already wrapped somewhere along the line?
if (unwrapped instanceof MetricsConfigurationWrapper)
{
// If so, we are done. no need to wrap again.
return configuration;
}
// Unwrap
unwrapped = ((Configuration.Wrapper)unwrapped).getWrapped();
}

return new MetricsConfigurationWrapper(configuration, (WebAppMetricsListener)metricsListener);
}

@Override
public EventListener wrapEventListener(EventListener listener)
{
Expand Down
Expand Up @@ -18,7 +18,6 @@

package org.eclipse.jetty.metrics;

import java.time.Duration;
import javax.servlet.ServletContext;

import org.eclipse.jetty.webapp.Configuration;
Expand All @@ -32,14 +31,22 @@ enum ConfigurationStep
}

/**
* Timing for a specific {@link Configuration} being applied to the {@link WebAppContext}
* Event that a specific {@link Configuration} being applied to the {@link WebAppContext}
*
* @param context the specific context that was the configuration was applied to
* @param configuration the configuration that was applied
* @param configurationStep the configuration step
* @param duration the duration for this configuration step
*/
void onWebAppConfigureTiming(WebAppContext context, Configuration configuration, ConfigurationStep configurationStep, Duration duration);
void onWebAppConfigureStart(WebAppContext context, Configuration configuration, ConfigurationStep configurationStep);

/**
* Event that a specific {@link Configuration} being applied to the {@link WebAppContext} has completed
*
* @param context the specific context that was the configuration was applied to
* @param configuration the configuration that was applied
* @param configurationStep the configuration step
*/
void onWebAppConfigureFinished(WebAppContext context, Configuration configuration, ConfigurationStep configurationStep);

/**
* Event that the WebAppContext has started to be initialized
Expand Down
Expand Up @@ -19,10 +19,12 @@
package org.eclipse.jetty.metrics;

import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet
{
@Override
Expand Down
Expand Up @@ -88,7 +88,7 @@ public void testSimpleServlet() throws Exception
expectedEvents.add("onServletEnter()");
expectedEvents.add("onServletExit()");

assertThat("Metrics Events Count", captureListener.events.size(), is(expectedEvents.size()));
assertThat("Metrics Events Count", captureListener.getEvents().size(), is(expectedEvents.size()));
}

@Test
Expand Down Expand Up @@ -122,6 +122,6 @@ public void testSimpleFilterAndServlet() throws Exception
expectedEvents.add("onServletExit()");
expectedEvents.add("onFilterExit()");

assertThat("Metrics Events Count", captureListener.events.size(), is(expectedEvents.size()));
assertThat("Metrics Events Count", captureListener.getEvents().size(), is(expectedEvents.size()));
}
}
Expand Up @@ -31,9 +31,9 @@
public class ServletMetricsCaptureListener implements ServletMetricsListener
{
private static final Logger LOG = Log.getLogger(ServletMetricsCaptureListener.class);
public LinkedBlockingQueue<String> events = new LinkedBlockingQueue<>();
private LinkedBlockingQueue<String> events = new LinkedBlockingQueue<>();

private void addEvent(String format, Object... args)
protected void addEvent(String format, Object... args)
{
String eventText = String.format(format, args);
events.offer(eventText);
Expand All @@ -46,6 +46,11 @@ private void addEvent(String format, Object... args)
LOG.info("[EVENT] {}", eventText, cause);
}

public LinkedBlockingQueue<String> getEvents()
{
return events;
}

@Override
public void onServletContextStarting(ServletContext servletContext)
{
Expand Down
@@ -0,0 +1,175 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.metrics;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

@ExtendWith(WorkDirExtension.class)
public class WebAppContextMetricsTest
{
public WorkDir workDir;
private Server server;
private HttpClient client;

@BeforeEach
public void setUp() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);

client = new HttpClient();
client.start();
}

@AfterEach
public void tearDown()
{
LifeCycle.stop(client);
LifeCycle.stop(server);
}

private void enableByteCodeScanning()
{
Configuration.ClassList classlist = Configuration.ClassList
.setServerDefault(server);

classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
}

@Test
public void testBasicBytecodeScan() throws Exception
{
Path webappDir = workDir.getEmptyPathDir();
Path webinfDir = webappDir.resolve("WEB-INF");
Path classesDir = webinfDir.resolve("classes");
FS.ensureDirExists(classesDir);
copyClassToWebInfClasses(HelloServlet.class, classesDir);

enableByteCodeScanning();

WebAppMetricsCaptureListener captureListener = new WebAppMetricsCaptureListener();
MetricsHandler metricsHandler = new MetricsHandler(captureListener);

WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.addBean(metricsHandler);
webapp.setWarResource(new PathResource(webappDir));

metricsHandler.addToAllConnectors(server);
metricsHandler.addToContext(webapp);
server.setHandler(webapp);
server.start();

ContentResponse response = client.GET(server.getURI().resolve("/hello"));
assertThat("Response.status", response.getStatus(), is(HttpStatus.OK_200));

Class<?>[] configurationClasses = {
WebInfConfiguration.class,
WebXmlConfiguration.class,
MetaInfConfiguration.class,
FragmentConfiguration.class,
AnnotationConfiguration.class,
JettyWebXmlConfiguration.class
};

List<String> expectedEvents = new ArrayList<>();
expectedEvents.add("onWebAppStarting()");
expectedEvents.add("onServletContextStarting()");
addExpectedConfigurations(expectedEvents, WebAppMetricsListener.ConfigurationStep.PRE, configurationClasses);
addExpectedConfigurations(expectedEvents, WebAppMetricsListener.ConfigurationStep.MAIN, configurationClasses);
expectedEvents.add("onServletStarting() - DefaultServlet");
expectedEvents.add("onServletReady() - DefaultServlet");
expectedEvents.add("onServletStarting() - NoJspServlet");
expectedEvents.add("onServletReady() - NoJspServlet");
addExpectedConfigurations(expectedEvents, WebAppMetricsListener.ConfigurationStep.POST, configurationClasses);
expectedEvents.add("onWebAppReady()");
expectedEvents.add("onServletContextReady()");
expectedEvents.add("onServletStarting() - HelloServlet");
expectedEvents.add("onServletReady() - HelloServlet");
expectedEvents.add("onServletEnter()");
expectedEvents.add("onServletExit()");

assertThat("Metrics Events Count", captureListener.getEvents().size(), is(expectedEvents.size()));
}

private void addExpectedConfigurations(List<String> expectedEvents, WebAppMetricsListener.ConfigurationStep configStep, Class<?>[] configurationClasses)
{
for (Class<?> configClass : configurationClasses)
{
expectedEvents.add(String.format("onWebAppConfigureStart() - %s:%s", configStep, configClass.getName()));
expectedEvents.add(String.format("onWebAppConfigureFinished() - %s:%s", configStep, configClass.getName()));
}
}

private void copyClassToWebInfClasses(Class<?> clazz, Path classesDir) throws IOException
{
String classRef = clazz.getName().replace('.', '/') + ".class";
URL url = this.getClass().getResource('/' + classRef);
MatcherAssert.assertThat("URL to class " + classRef, url, is(not(nullValue())));
Path destPath = classesDir.resolve(classRef);
FS.ensureDirExists(destPath.getParent());
try (InputStream in = url.openStream();
OutputStream out = Files.newOutputStream(destPath))
{
IO.copy(in, out);
}
}
}

0 comments on commit eced4cc

Please sign in to comment.