Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class RestOpenApiProcessor extends AsyncProcessorSupport implements Camel
private PlatformHttpConsumerAware platformHttpConsumer;
private Consumer consumer;
private OpenApiUtils openApiUtils;
private RestRegistry restRegistry;

public RestOpenApiProcessor(RestOpenApiEndpoint endpoint, OpenAPI openAPI, String basePath, String apiContextPath,
RestOpenapiProcessorStrategy restOpenapiProcessorStrategy) {
Expand Down Expand Up @@ -111,6 +112,10 @@ public boolean process(Exchange exchange, AsyncCallback callback) {
// map path-parameters from operation to camel headers
HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, consumerPath);

if (restRegistry != null) {
restRegistry.hit(verb, basePath, consumerPath);
}

// process the incoming request
return restOpenapiProcessorStrategy.process(openAPI, o, verb, path, rcp.getBinding(), exchange, callback);
}
Expand Down Expand Up @@ -150,6 +155,7 @@ public void afterPropertiesConfigured(CamelContext camelContext) {
// this is required to build the paths with all the details

this.openApiUtils = new OpenApiUtils(camelContext, endpoint.getBindingPackageScan(), openAPI.getComponents());
this.restRegistry = PluginHelper.getRestRegistry(camelContext);
// register all openapi paths
for (var e : openAPI.getPaths().entrySet()) {
String path = e.getKey(); // path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
package org.apache.camel.component.rest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
Expand All @@ -32,18 +33,20 @@
import org.apache.camel.Service;
import org.apache.camel.ServiceStatus;
import org.apache.camel.StatefulService;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.CamelEvent.ExchangeCreatedEvent;
import org.apache.camel.spi.NormalizedEndpointUri;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.spi.RestRegistry;
import org.apache.camel.support.EventNotifierSupport;
import org.apache.camel.support.LifecycleStrategySupport;
import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.ObjectHelper;

public class DefaultRestRegistry extends ServiceSupport implements RestRegistry, CamelContextAware {
public class DefaultRestRegistry extends EventNotifierSupport implements RestRegistry {

private CamelContext camelContext;
private final Map<Consumer, List<RestService>> registry = new LinkedHashMap<>();
private final Map<Consumer, List<RestService>> specs = new LinkedHashMap<>();
private final Map<String, List<RestServiceEntry>> routeIdIndex = new HashMap<>();
private transient Producer apiProducer;

@Override
Expand All @@ -57,6 +60,9 @@ public void addRestService(
outType, routeId, operationId, specificationUri, description);
List<RestService> list = registry.computeIfAbsent(consumer, c -> new ArrayList<>());
list.add(entry);
if (routeId != null) {
routeIdIndex.computeIfAbsent(routeId, k -> new ArrayList<>()).add(entry);
}
}

@Override
Expand All @@ -70,9 +76,55 @@ public void addRestSpecification(
list.add(entry);
}

@Override
public void hit(String method, String basePath, String path) {
for (var list : registry.values()) {
for (var rs : list) {
RestServiceEntry entry = (RestServiceEntry) rs;
if (entry.method.equalsIgnoreCase(method) && matchesPath(entry, basePath, path)) {
entry.hits.incrementAndGet();
return;
}
}
}
}

private static boolean matchesPath(RestServiceEntry entry, String basePath, String path) {
if (entry.basePath != null && entry.basePath.equals(basePath)) {
if (path != null && path.equals(entry.uriTemplate)) {
return true;
}
// contract-first stores the OpenAPI path in baseUrl with uriTemplate=null
if (entry.uriTemplate == null && path != null && path.equals(entry.baseUrl)) {
return true;
}
String entryPath = entry.basePath + (entry.uriTemplate != null ? entry.uriTemplate : "");
return path != null && path.equals(entryPath);
}
String entryPath = entry.basePath != null ? entry.basePath : "";
if (entry.uriTemplate != null) {
entryPath += entry.uriTemplate;
}
return path != null && path.equals(entryPath);
}

@Override
public void removeRestService(Consumer consumer) {
registry.remove(consumer);
List<RestService> removed = registry.remove(consumer);
if (removed != null) {
for (RestService rs : removed) {
RestServiceEntry entry = (RestServiceEntry) rs;
if (entry.routeId != null) {
List<RestServiceEntry> entries = routeIdIndex.get(entry.routeId);
if (entries != null) {
entries.remove(entry);
if (entries.isEmpty()) {
routeIdIndex.remove(entry.routeId);
}
}
}
}
}
specs.remove(consumer);
}

Expand Down Expand Up @@ -106,6 +158,7 @@ public int size() {
@Override
public String apiDocAsJson() {
// see if there is a rest-api endpoint which would be the case if rest api-doc has been explicit enabled
CamelContext camelContext = getCamelContext();
if (apiProducer == null) {
Endpoint restApiEndpoint = null;
Endpoint restEndpoint = null;
Expand Down Expand Up @@ -164,26 +217,42 @@ public String apiDocAsJson() {
}

@Override
public CamelContext getCamelContext() {
return camelContext;
public void notify(CamelEvent event) throws Exception {
if (event instanceof ExchangeCreatedEvent ece) {
String routeId = ece.getExchange().getFromRouteId();
if (routeId != null) {
List<RestServiceEntry> entries = routeIdIndex.get(routeId);
if (entries != null) {
for (RestServiceEntry entry : entries) {
if (!entry.contractFirst) {
entry.hits.incrementAndGet();
}
}
}
}
}
}

@Override
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
public boolean isEnabled(CamelEvent event) {
return event instanceof ExchangeCreatedEvent;
}

@Override
protected void doStart() throws Exception {
ObjectHelper.notNull(camelContext, "camelContext", this);
ObjectHelper.notNull(getCamelContext(), "camelContext", this);
// add a lifecycle so we can keep track when consumers is being removed, so we can unregister them from our registry
camelContext.addLifecycleStrategy(new RemoveRestServiceLifecycleStrategy());
getCamelContext().addLifecycleStrategy(new RemoveRestServiceLifecycleStrategy());
// register as event notifier so we receive ExchangeCreatedEvent for code-first hit tracking
getCamelContext().getManagementStrategy().addEventNotifier(this);
}

@Override
protected void doStop() throws Exception {
getCamelContext().getManagementStrategy().removeEventNotifier(this);
registry.clear();
specs.clear();
routeIdIndex.clear();
}

/**
Expand All @@ -207,6 +276,7 @@ private static final class RestServiceEntry implements RestService {
private final String operationId;
private final String specificationUri;
private final String description;
private final AtomicLong hits = new AtomicLong();

private RestServiceEntry(Consumer consumer, boolean specification, boolean contractFirst, String url, String baseUrl,
String basePath,
Expand Down Expand Up @@ -324,6 +394,11 @@ public String getSpecificationUri() {
public String getDescription() {
return description;
}

@Override
public long getHits() {
return hits.get();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ interface RestService {
@Nullable
String getSpecificationUri();

/**
* Number of requests processed by this REST service.
*
* @since 4.21
*/
long getHits();

}

/**
Expand Down Expand Up @@ -166,6 +173,16 @@ void addRestService(
String routeId, @Nullable String operationId, @Nullable String specificationUri,
@Nullable String description);

/**
* Records a hit on the REST service matching the given HTTP method and path.
*
* @param method the HTTP method (GET, POST, etc.)
* @param basePath the base path
* @param path the URI path or template (e.g. /users/{id})
* @since 4.21
*/
void hit(String method, String basePath, String path);

/**
* Removes the REST service from the registry
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
import java.util.Set;

import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.CamelEvent.ExchangeCompletedEvent;
import org.apache.camel.spi.CamelEvent.ExchangeCreatedEvent;
import org.apache.camel.spi.CamelEvent.ExchangeFailedEvent;
import org.apache.camel.spi.CamelEvent.ExchangeSendingEvent;
import org.apache.camel.spi.CamelEvent.RouteAddedEvent;
import org.apache.camel.spi.CamelEvent.RouteRemovedEvent;
Expand Down Expand Up @@ -295,6 +298,28 @@ public void notify(CamelEvent event) throws Exception {
outputUtilization.onHit(key);
}
}
} else if (event instanceof ExchangeCompletedEvent || event instanceof ExchangeFailedEvent) {
// InOut consumers send a reply back when the exchange completes;
// record this as an "out" hit on the consumer's fromEndpoint
CamelEvent.ExchangeEvent ee = (CamelEvent.ExchangeEvent) event;
Exchange exchange = ee.getExchange();
if (exchange.getPattern() != null && exchange.getPattern().isOutCapable()) {
Endpoint endpoint = exchange.getFromEndpoint();
if (endpoint != null) {
String routeId = exchange.getFromRouteId();
String uri = endpoint.getEndpointUri();
Map<String, String> uris = outputs.get(routeId);
if (uris != null) {
uris.putIfAbsent(uri, uri);
}
if (extended) {
String key = asUtilizationKey(routeId, uri);
if (key != null) {
outputUtilization.onHit(key);
}
}
}
}
}
}

Expand All @@ -307,6 +332,8 @@ public boolean isDisabled() {
public boolean isEnabled(CamelEvent event) {
return enabled && event instanceof ExchangeCreatedEvent
|| event instanceof ExchangeSendingEvent
|| event instanceof ExchangeCompletedEvent
|| event instanceof ExchangeFailedEvent
|| event instanceof RouteAddedEvent
|| event instanceof RouteRemovedEvent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ protected Map<String, Object> doCallJson(Map<String, Object> options) {
if (rs.getDescription() != null) {
jo.put("description", rs.getDescription());
}
long hits = rs.getHits();
if (hits > 0) {
jo.put("hits", hits);
}
list.add(jo);
}
}
Expand Down
Loading