Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
<packaging>jar</packaging>
<name>rest-utils</name>

<properties>
<junit.version>4.11</junit.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
Expand Down
81 changes: 54 additions & 27 deletions core/src/main/java/io/confluent/rest/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
Expand All @@ -30,42 +31,48 @@
import org.glassfish.jersey.server.validation.ValidationFeature;
import org.glassfish.jersey.servlet.ServletContainer;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.ws.rs.core.Configurable;

import io.confluent.common.metrics.JmxReporter;
import io.confluent.common.metrics.MetricConfig;
import io.confluent.common.metrics.Metrics;
import io.confluent.common.metrics.MetricsReporter;
import io.confluent.rest.exceptions.ConstraintViolationExceptionMapper;
import io.confluent.rest.exceptions.GenericExceptionMapper;
import io.confluent.rest.exceptions.WebApplicationExceptionMapper;
import io.confluent.rest.logging.Slf4jRequestLog;
import io.confluent.rest.metrics.MetricsResourceMethodApplicationListener;
import io.confluent.rest.metrics.MetricsSelectChannelConnector;
import io.confluent.rest.validation.JacksonMessageBodyProvider;

/**
* A REST application. Extend this class and implement the configure() method to generate your
* application-specific configuration class and setupResources() to register REST resources with the
* JAX-RS server. Use createServer() to get a fully-configured, ready to run Jetty server.
* A REST application. Extend this class and implement setupResources() to register REST
* resources with the JAX-RS server. Use createServer() to get a fully-configured, ready to run
* Jetty server.
*/
public abstract class Application<T extends RestConfig> {
protected T config;
protected Server server = null;
protected CountDownLatch shutdownLatch = new CountDownLatch(1);

public Application() {}
protected Metrics metrics;

public Application(T config) {
this.config = config;
}

/**
* Parse, load, or generate the Configuration for this application.
*/
public T configure() throws RestConfigException {
// Allow this implementation as a nop if they provide
if (this.config == null)
throw new RestConfigException(
"Application.configure() was not overridden for " + getClass().getName() +
" but the configuration was not passed to the Application class's constructor.");
return this.config;
MetricConfig metricConfig = new MetricConfig()
.samples(config.getInt(RestConfig.METRICS_NUM_SAMPLES_CONFIG))
.timeWindow(config.getLong(RestConfig.METRICS_SAMPLE_WINDOW_MS_CONFIG),
TimeUnit.MILLISECONDS);
List<MetricsReporter> reporters =
config.getConfiguredInstances(RestConfig.METRICS_REPORTER_CLASSES_CONFIG,
MetricsReporter.class);
reporters.add(new JmxReporter(config.getString(RestConfig.METRICS_JMX_PREFIX_CONFIG)));
this.metrics = new Metrics(metricConfig, reporters, config.getTime());
}

/**
Expand All @@ -75,32 +82,45 @@ public T configure() throws RestConfigException {
*/
public abstract void setupResources(Configurable<?> config, T appConfig);

/**
* Returns a map of tag names to tag values to apply to metrics for this application.
*
* @return a Map of tags and values
*/
public Map<String,String> getMetricsTags() {
return new LinkedHashMap<String, String>();
}

/**
* Configure and create the server.
*/
public Server createServer() throws RestConfigException {
if (config == null) {
configure();
}

// The configuration for the JAX-RS REST service
ResourceConfig resourceConfig = new ResourceConfig();

configureBaseApplication(resourceConfig);
Map<String, String> metricTags = getMetricsTags();

configureBaseApplication(resourceConfig, metricTags);
setupResources(resourceConfig, getConfiguration());

// Configure the servlet container
ServletContainer servletContainer = new ServletContainer(resourceConfig);
ServletHolder servletHolder = new ServletHolder(servletContainer);
server = new Server(getConfiguration().getInt(RestConfig.PORT_CONFIG)) {
server = new Server() {
@Override
protected void doStop() throws Exception {
super.doStop();
Application.this.metrics.close();
Application.this.onShutdown();
Application.this.shutdownLatch.countDown();
}
};

int port = getConfiguration().getInt(RestConfig.PORT_CONFIG);
MetricsSelectChannelConnector connector = new MetricsSelectChannelConnector(
port, metrics, "jetty", metricTags);
server.setConnectors(new Connector[]{connector});

ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.addServlet(servletHolder, "/*");
Expand All @@ -124,13 +144,17 @@ protected void doStop() throws Exception {
return server;
}

public void configureBaseApplication(Configurable<?> config) {
configureBaseApplication(config, null);
}

/**
* Register standard components for a JSON REST application on the given JAX-RS configurable,
* which can be either an ResourceConfig for a server or a ClientConfig for a Jersey-based REST
* client.
*/
public void configureBaseApplication(Configurable<?> config) {
RestConfig restRestConfig = getConfiguration();
public void configureBaseApplication(Configurable<?> config, Map<String, String> metricTags) {
RestConfig restConfig = getConfiguration();

ObjectMapper jsonMapper = getJsonMapper();
JacksonMessageBodyProvider jsonProvider = new JacksonMessageBodyProvider(jsonMapper);
Expand All @@ -139,8 +163,11 @@ public void configureBaseApplication(Configurable<?> config) {

config.register(ValidationFeature.class);
config.register(ConstraintViolationExceptionMapper.class);
config.register(new WebApplicationExceptionMapper(restRestConfig));
config.register(new GenericExceptionMapper(restRestConfig));
config.register(new WebApplicationExceptionMapper(restConfig));
config.register(new GenericExceptionMapper(restConfig));

config.register(new MetricsResourceMethodApplicationListener(metrics, "jersey",
metricTags, restConfig.getTime()));

config.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}
Expand Down
48 changes: 47 additions & 1 deletion core/src/main/java/io/confluent/rest/RestConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import io.confluent.common.config.ConfigDef;
import io.confluent.common.config.ConfigDef.Type;
import io.confluent.common.config.ConfigDef.Importance;
import io.confluent.common.utils.SystemTime;
import io.confluent.common.utils.Time;

import java.util.Map;
import java.util.TreeMap;
Expand Down Expand Up @@ -56,6 +58,31 @@ public class RestConfig extends AbstractConfig {
"Name of the SLF4J logger to write the NCSA Common Log Format request log.";
protected static final String REQUEST_LOGGER_NAME_DEFAULT = "io.confluent.rest-utils.requests";

public static final String METRICS_JMX_PREFIX_CONFIG = "metrics.jmx.prefix";
protected static final String METRICS_JMX_PREFIX_DOC =
"Prefix to apply to metric names for the default JMX reporter.";
protected static final String METRICS_JMX_PREFIX_DEFAULT = "rest-utils";

public static final String METRICS_SAMPLE_WINDOW_MS_CONFIG = "metrics.sample.window.ms";
protected static final String METRICS_SAMPLE_WINDOW_MS_DOC =
"The metrics system maintains a configurable number of samples over a fixed window size. " +
"This configuration controls the size of the window. For example we might maintain two " +
"samples each measured over a 30 second period. When a window expires we erase and " +
"overwrite the oldest window.";
protected static final long METRICS_SAMPLE_WINDOW_MS_DEFAULT = 30000;

public static final String METRICS_NUM_SAMPLES_CONFIG = "metrics.num.samples";
protected static final String METRICS_NUM_SAMPLES_DOC =
"The number of samples maintained to compute metrics.";
protected static final int METRICS_NUM_SAMPLES_DEFAULT = 2;

public static final String METRICS_REPORTER_CLASSES_CONFIG = "metric.reporters";
protected static final String METRICS_REPORTER_CLASSES_DOC =
"A list of classes to use as metrics reporters. Implementing the " +
"<code>MetricReporter</code> interface allows plugging in classes that will be notified " +
"of new metric creation. The JmxReporter is always included to register JMX statistics.";
protected static final String METRICS_REPORTER_CLASSES_DEFAULT = "";

public static ConfigDef baseConfigDef() {
return new ConfigDef()
.define(DEBUG_CONFIG, Type.BOOLEAN,
Expand All @@ -73,14 +100,33 @@ public static ConfigDef baseConfigDef() {
SHUTDOWN_GRACEFUL_MS_DOC)
.define(REQUEST_LOGGER_NAME_CONFIG, Type.STRING,
REQUEST_LOGGER_NAME_DEFAULT, Importance.LOW,
REQUEST_LOGGER_NAME_DOC);
REQUEST_LOGGER_NAME_DOC)
.define(METRICS_JMX_PREFIX_CONFIG, Type.STRING,
METRICS_JMX_PREFIX_DEFAULT, Importance.LOW, METRICS_JMX_PREFIX_DOC)
.define(METRICS_REPORTER_CLASSES_CONFIG, Type.LIST,
METRICS_REPORTER_CLASSES_DEFAULT, Importance.LOW, METRICS_REPORTER_CLASSES_DOC)
.define(METRICS_SAMPLE_WINDOW_MS_CONFIG,
Type.LONG,
METRICS_SAMPLE_WINDOW_MS_DEFAULT,
ConfigDef.Range.atLeast(0),
Importance.LOW,
METRICS_SAMPLE_WINDOW_MS_DOC)
.define(METRICS_NUM_SAMPLES_CONFIG, Type.INT,
METRICS_NUM_SAMPLES_DEFAULT, ConfigDef.Range.atLeast(1),
Importance.LOW, METRICS_NUM_SAMPLES_DOC);
}

private static Time defaultTime = new SystemTime();

public RestConfig(ConfigDef definition, Map<?, ?> originals) {
super(definition, originals);
}

public RestConfig(ConfigDef definition) {
super(definition, new TreeMap<Object,Object>());
}

public Time getTime() {
return defaultTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright 2015 Confluent Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

package io.confluent.rest.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that specifies that performance metrics should be collected for the annotated
* resource.
*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR,
ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface PerformanceMetric {
/**
* Placeholder that specifies the name should use a default value, such as the class/method
* name of the resource.
*/
public static String DEFAULT_NAME = "__DEFAULT_NAME__";

String value() default DEFAULT_NAME;
}
Loading