/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.monitor.extensions.dashboard;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.wisdom.api.configuration.ApplicationConfiguration;
import org.wisdom.api.http.Result;
import org.wisdom.api.http.Status;
import org.wisdom.api.interception.Filter;
import org.wisdom.api.interception.RequestContext;
import org.wisdom.api.router.Route;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
/**
* A class exposing a HTTP Request filter to compute HTTP metrics.
*/
public class HttpMetricFilter implements Filter, Status {
private final BundleContext context;
private final Pattern interceptionPattern;
private final Integer interceptionPriority;
private ServiceRegistration<Filter> reg;
// initialized after call of init method
private ConcurrentMap<Integer, Meter> metersByStatusCode;
private Meter otherMeter;
private Counter activeRequests;
private Timer requestTimer;
/**
* Creates a new instance of the filter.
*
* @param context the bundle context
* @param configuration the application configuration
* @param registry the metric registry
*/
public HttpMetricFilter(BundleContext context, ApplicationConfiguration configuration,
MetricRegistry registry) {
this.context = context;
Map<Integer, String> meterNamesByStatusCode = createMeterNamesByStatusCode();
this.interceptionPattern = Pattern.compile(configuration.getWithDefault("monitor.http.interception", ".*"));
this.interceptionPriority = configuration.getIntegerWithDefault("monitor.http.priority", 10000);
this.metersByStatusCode = new ConcurrentHashMap<>(meterNamesByStatusCode
.size());
for (Map.Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) {
metersByStatusCode.put(entry.getKey(),
registry.meter("http.responseCodes." + entry.getValue()));
}
this.otherMeter = registry.meter("http.responseCodes.others");
this.activeRequests = registry.counter("http.activeRequests");
this.requestTimer = registry.timer("http.requests");
}
private static Map<Integer, String> createMeterNamesByStatusCode() {
final Map<Integer, String> meterNamesByStatusCode = new HashMap<>(6);
meterNamesByStatusCode.put(OK, "ok");
meterNamesByStatusCode.put(NOT_MODIFIED, "notModified");
meterNamesByStatusCode.put(BAD_REQUEST, "badRequest");
meterNamesByStatusCode.put(NOT_FOUND, "notFound");
meterNamesByStatusCode.put(INTERNAL_SERVER_ERROR, "serverError");
return meterNamesByStatusCode;
}
/**
* Starts the filter.
*/
public void start() {
reg = context.registerService(Filter.class, this, null);
}
/**
* Stops the filter.
*/
public void stop() {
if (reg != null) {
reg.unregister();
reg = null;
}
}
/**
* The interception method. The method should call {@link org.wisdom.api.interception.RequestContext#proceed()}
* to call the next interceptor. Without this call it cuts the chain.
*
* @param route the route
* @param context the filter context
* @return the result
* @throws Exception if anything bad happen
*/
@Override
public Result call(Route route, RequestContext context) throws Exception {
activeRequests.inc();
final Timer.Context ctxt = requestTimer.time();
Result result = null;
try {
result = context.proceed();
return result;
} finally {
ctxt.stop();
activeRequests.dec();
markMeterForStatusCode(result);
}
}
private void markMeterForStatusCode(Result result) {
if (result == null) {
otherMeter.mark();
return;
}
final Meter metric = metersByStatusCode.get(result.getStatusCode());
if (metric != null) {
metric.mark();
} else {
otherMeter.mark();
}
}
/**
* Gets the Regex Pattern used to determine whether the route is handled by the filter or not.
* Notice that the router are caching these patterns and so cannot changed.
*/
@Override
public Pattern uri() {
return interceptionPattern;
}
/**
* Gets the filter priority, determining the position of the filter in the filter chain. Filter with a high
* priority are called first. Notice that the router are caching these priorities and so cannot changed.
* <p/>
* It is heavily recommended to allow configuring the priority from the Application Configuration.
*
* @return the priority
*/
@Override
public int priority() {
return interceptionPriority;
}
}