Skip to content

Commit

Permalink
FEAT: Jersey: check impl method for annotations before interface method
Browse files Browse the repository at this point in the history
Metrics annotations (@timed, @metered, etc) don’t allow annotating a
resource on the *implementing* method, only on the *defining* (interface)
method (or more accurately, the method corresponding to the @path
definition, more or less).

This patch enhances dropwizard-metrics to extract metrics annotations more
intelligently, preferring the annotation on the implementation (and
falling back to the interface / definitionMethod if the implementation
annotation is absent).
  • Loading branch information
Mark Miller committed Aug 23, 2023
1 parent d7b813f commit d160b34
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,33 @@ private <T extends Annotation> T getClassLevelAnnotation(final Resource resource
return annotation;
}

/**
* @param method the method to examine for the annotation
* @param annotation the annotation class (e.g. Timed, Metered, etc)
* @return the annotation (of type {@code T})from the given method, or null.
* @param <T> the type of annotation to return
*/
private static <T extends Annotation> T findAnnotation(final ResourceMethod method, final Class<T> annotation) {
// In code-generation situations, developers define their API (via OpenAPI or gRPC or ___), and JAX-RS
// interfaces are generated for the defined services (which define @Path, @Produces, etc).
// The developer is then responsible for implementing those service interfaces.
// For fetching metrics annotations, we presume that any annotation on the implementing method should
// take precedence over the interface method, as the developer controls the former more directly.
T result = method.getInvocable().getHandlingMethod().getAnnotation(annotation);
if (result == null) {
result = method.getInvocable().getDefinitionMethod().getAnnotation(annotation);
}
return result;
}

private void registerTimedAnnotations(final ResourceMethod method, final Timed classLevelTimed) {
final Method definitionMethod = method.getInvocable().getDefinitionMethod();
if (classLevelTimed != null) {
registerTimers(method, definitionMethod, classLevelTimed);
return;
}

final Timed annotation = definitionMethod.getAnnotation(Timed.class);
final Timed annotation = findAnnotation(method, Timed.class);
if (annotation != null) {
registerTimers(method, definitionMethod, annotation);
}
Expand All @@ -434,7 +453,7 @@ private void registerMeteredAnnotations(final ResourceMethod method, final Meter
meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, classLevelMetered));
return;
}
final Metered annotation = definitionMethod.getAnnotation(Metered.class);
final Metered annotation = findAnnotation(method, Metered.class);

if (annotation != null) {
meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, annotation));
Expand All @@ -448,7 +467,7 @@ private void registerExceptionMeteredAnnotations(final ResourceMethod method, fi
exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, classLevelExceptionMetered));
return;
}
final ExceptionMetered annotation = definitionMethod.getAnnotation(ExceptionMetered.class);
final ExceptionMetered annotation = findAnnotation(method, ExceptionMetered.class);

if (annotation != null) {
exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
Expand All @@ -462,7 +481,7 @@ private void registerResponseMeteredAnnotations(final ResourceMethod method, fin
responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, classLevelResponseMetered));
return;
}
final ResponseMetered annotation = definitionMethod.getAnnotation(ResponseMetered.class);
final ResponseMetered annotation = findAnnotation(method, ResponseMetered.class);

if (annotation != null) {
responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, annotation));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,33 @@ private <T extends Annotation> T getClassLevelAnnotation(final Resource resource
return annotation;
}

/**
* @param method the method to examine for the annotation
* @param annotation the annotation class (e.g. Timed, Metered, etc)
* @return the annotation (of type {@code T})from the given method, or null.
* @param <T> the type of annotation to return
*/
private static <T extends Annotation> T findAnnotation(final ResourceMethod method, final Class<T> annotation) {
// In code-generation situations, developers define their API (via OpenAPI or gRPC or ___), and JAX-RS
// interfaces are generated for the defined services (which define @Path, @Produces, etc).
// The developer is then responsible for implementing those service interfaces.
// For fetching metrics annotations, we presume that any annotation on the implementing method should
// take precedence over the interface method, as the developer controls the former more directly.
T result = method.getInvocable().getHandlingMethod().getAnnotation(annotation);
if (result == null) {
result = method.getInvocable().getDefinitionMethod().getAnnotation(annotation);
}
return result;
}

private void registerTimedAnnotations(final ResourceMethod method, final Timed classLevelTimed) {
final Method definitionMethod = method.getInvocable().getDefinitionMethod();
if (classLevelTimed != null) {
registerTimers(method, definitionMethod, classLevelTimed);
return;
}

final Timed annotation = definitionMethod.getAnnotation(Timed.class);
final Timed annotation = findAnnotation(method, Timed.class);
if (annotation != null) {
registerTimers(method, definitionMethod, annotation);
}
Expand All @@ -435,7 +454,7 @@ private void registerMeteredAnnotations(final ResourceMethod method, final Meter
meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, classLevelMetered));
return;
}
final Metered annotation = definitionMethod.getAnnotation(Metered.class);
final Metered annotation = findAnnotation(method, Metered.class);

if (annotation != null) {
meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, annotation));
Expand All @@ -449,7 +468,7 @@ private void registerExceptionMeteredAnnotations(final ResourceMethod method, fi
exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, classLevelExceptionMetered));
return;
}
final ExceptionMetered annotation = definitionMethod.getAnnotation(ExceptionMetered.class);
final ExceptionMetered annotation = findAnnotation(method, ExceptionMetered.class);

if (annotation != null) {
exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
Expand All @@ -463,7 +482,7 @@ private void registerResponseMeteredAnnotations(final ResourceMethod method, fin
responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, classLevelResponseMetered));
return;
}
final ResponseMetered annotation = definitionMethod.getAnnotation(ResponseMetered.class);
final ResponseMetered annotation = findAnnotation(method, ResponseMetered.class);

if (annotation != null) {
responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, annotation));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,33 @@ private <T extends Annotation> T getClassLevelAnnotation(final Resource resource
return annotation;
}

/**
* @param method the method to examine for the annotation
* @param annotation the annotation class (e.g. Timed, Metered, etc)
* @return the annotation (of type {@code T})from the given method, or null.
* @param <T> the type of annotation to return
*/
private static <T extends Annotation> T findAnnotation(final ResourceMethod method, final Class<T> annotation) {
// In code-generation situations, developers define their API (via OpenAPI or gRPC or ___), and JAX-RS
// interfaces are generated for the defined services (which define @Path, @Produces, etc).
// The developer is then responsible for implementing those service interfaces.
// For fetching metrics annotations, we presume that any annotation on the implementing method should
// take precedence over the interface method, as the developer controls the former more directly.
T result = method.getInvocable().getHandlingMethod().getAnnotation(annotation);
if (result == null) {
result = method.getInvocable().getDefinitionMethod().getAnnotation(annotation);
}
return result;
}

private void registerTimedAnnotations(final ResourceMethod method, final Timed classLevelTimed) {
final Method definitionMethod = method.getInvocable().getDefinitionMethod();
if (classLevelTimed != null) {
registerTimers(method, definitionMethod, classLevelTimed);
return;
}

final Timed annotation = definitionMethod.getAnnotation(Timed.class);
final Timed annotation = findAnnotation(method, Timed.class);
if (annotation != null) {
registerTimers(method, definitionMethod, annotation);
}
Expand All @@ -434,7 +453,7 @@ private void registerMeteredAnnotations(final ResourceMethod method, final Meter
meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, classLevelMetered));
return;
}
final Metered annotation = definitionMethod.getAnnotation(Metered.class);
final Metered annotation = findAnnotation(method, Metered.class);

if (annotation != null) {
meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, annotation));
Expand All @@ -448,7 +467,7 @@ private void registerExceptionMeteredAnnotations(final ResourceMethod method, fi
exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, classLevelExceptionMetered));
return;
}
final ExceptionMetered annotation = definitionMethod.getAnnotation(ExceptionMetered.class);
final ExceptionMetered annotation = findAnnotation(method, ExceptionMetered.class);

if (annotation != null) {
exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
Expand All @@ -462,7 +481,7 @@ private void registerResponseMeteredAnnotations(final ResourceMethod method, fin
responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, classLevelResponseMetered));
return;
}
final ResponseMetered annotation = definitionMethod.getAnnotation(ResponseMetered.class);
final ResponseMetered annotation = findAnnotation(method, ResponseMetered.class);

if (annotation != null) {
responseMeters.putIfAbsent(definitionMethod, new ResponseMeterMetric(metrics, method, annotation));
Expand Down

0 comments on commit d160b34

Please sign in to comment.