Jersey 2: Create a custom annotation to register resource filters

There is no direct equivalent of the @ResourceFilters annotation in Jersey 2.
The best alternative to define a custom annotation and use a DynamicFeature provider
to interpret this annotation and register filters.

Why not define a annotation for each filter and use Jersey's @NameBinding mechanism?
@ResourceFilters works differently than @NameBinding. A @ResourceFilters annotation
on method overrides the class level annotation; In this case, all filters
defined on the class level are discarded. This behaviour can't be achieved
with @NameBinding.

References:
https://eclipse-ee4j.github.io/jersey.github.io/documentation/2.37/filters-and-interceptors.html#d0e10127
https://jakartaee.github.io/rest/apidocs/2.1.6/javax/ws/rs/container/DynamicFeature.html
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java b/core/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java
index fd6fd1a..900272c 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/controller/AnnotationController.java
@@ -14,7 +14,7 @@
 import org.springframework.stereotype.Controller;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.dto.FoundryDto;
 import de.ids_mannheim.korap.dto.LayerDto;
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java b/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
index f19f9c8..18775b2 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
@@ -27,7 +27,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.config.KustvaktConfiguration;
 import de.ids_mannheim.korap.constant.OAuth2Scope;
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/controller/StatisticController.java b/core/src/main/java/de/ids_mannheim/korap/web/controller/StatisticController.java
index 229fb08..7ba31e4 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/controller/StatisticController.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/controller/StatisticController.java
@@ -19,7 +19,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.service.StatisticService;
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/APIVersionFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/APIVersionFilter.java
index 4039296..44d3114 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/APIVersionFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/APIVersionFilter.java
@@ -3,7 +3,6 @@
 import java.util.List;
 
 import javax.ws.rs.core.PathSegment;
-import javax.ws.rs.ext.Provider;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -21,7 +20,6 @@
  *
  */
 @Component
-@Provider
 public class APIVersionFilter
         implements ContainerRequestFilter {
 
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
index 30cf182..9149362 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
@@ -4,7 +4,6 @@
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.SecurityContext;
-import javax.ws.rs.ext.Provider;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -21,7 +20,6 @@
  * @see {@link AuthenticationFilter}
  */
 @Component
-@Provider
 public class AdminFilter extends AuthenticationFilter {
 
     private @Context ServletContext servletContext;
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
index 74224b0..26ec359 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
@@ -1,7 +1,5 @@
 package de.ids_mannheim.korap.web.filter;
 
-import javax.ws.rs.ext.Provider;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.glassfish.jersey.server.ContainerRequest;
@@ -30,7 +28,6 @@
  * @last update 12/2017
  */
 @Component
-@Provider
 public class AuthenticationFilter
         implements ContainerRequestFilter {
 
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
index 2704057..5db14e8 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
@@ -1,7 +1,5 @@
 package de.ids_mannheim.korap.web.filter;
 
-import javax.ws.rs.ext.Provider;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -21,7 +19,6 @@
  *       anonymous access should be allowed!
  */
 @Component
-@Provider
 public class BlockingFilter implements ContainerRequestFilter {
 
     @Autowired
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoFilter.java
index 4bb33d6..b63c799 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoFilter.java
@@ -3,7 +3,6 @@
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerRequestFilter;
 import javax.ws.rs.core.SecurityContext;
-import javax.ws.rs.ext.Provider;
 
 import org.glassfish.jersey.server.ContainerRequest;
 
@@ -17,7 +16,6 @@
  * @author hanl
  * @date 08/02/2016
  */
-@Provider
 public class DemoFilter implements ContainerRequestFilter {
 
     @Override
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
index 3d24717..deb93cb 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
@@ -6,7 +6,6 @@
 import javax.ws.rs.container.ContainerRequestFilter;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.UriInfo;
-import javax.ws.rs.ext.Provider;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -23,7 +22,6 @@
 /**
  * Created by hanl on 7/15/14.
  */
-@Provider
 @Component
 public class DemoUserFilter implements ContainerRequestFilter {
 
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java
index b17f7a7..a396b87 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/NonDemoBlockingFilter.java
@@ -1,7 +1,5 @@
 package de.ids_mannheim.korap.web.filter;
 
-import javax.ws.rs.ext.Provider;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -23,7 +21,6 @@
  *       anonymous access should be allowed!
  */
 @Component
-@Provider
 public class NonDemoBlockingFilter
         implements ContainerRequestFilter {
 
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/PiwikFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/PiwikFilter.java
index 26d84a8..adfdb3c 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/PiwikFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/PiwikFilter.java
@@ -9,7 +9,6 @@
 import java.util.Random;
 
 import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.ext.Provider;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -40,7 +39,6 @@
  * @date 13/05/2014
  */
 @Component
-@Provider
 public class PiwikFilter implements ContainerRequestFilter {
 
     private WebTarget service;
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/utils/ResourceFilters.java b/core/src/main/java/de/ids_mannheim/korap/web/utils/ResourceFilters.java
new file mode 100644
index 0000000..ab0d4ea
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/web/utils/ResourceFilters.java
@@ -0,0 +1,23 @@
+package de.ids_mannheim.korap.web.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Defines the list of {@link javax.ws.rs.container.ContainerRequestFilter}
+ * and {@link javax.ws.rs.container.ContainerResponseFilter}
+ * classes associated with a resource method.
+ * <p>
+ * This annotation can be specified on a class or on method(s). Specifying it
+ * at a class level means that it applies to all the methods in the class.
+ * Specifying it on a method means that it is applicable to that method only.
+ * If applied at both the class and methods level , the method value overrides
+ * the class value.
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ResourceFilters {
+    Class<?>[] value();
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/utils/ResourceFiltersFeature.java b/core/src/main/java/de/ids_mannheim/korap/web/utils/ResourceFiltersFeature.java
new file mode 100644
index 0000000..62b7745
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/web/utils/ResourceFiltersFeature.java
@@ -0,0 +1,27 @@
+package de.ids_mannheim.korap.web.utils;
+
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Registers {@link javax.ws.rs.container.ContainerRequestFilter}
+ * and {@link javax.ws.rs.container.ContainerResponseFilter}
+ * classes for a resource method annotated with {@link ResourceFilters}.
+ */
+@Provider
+public class ResourceFiltersFeature implements DynamicFeature {
+
+    @Override
+    public void configure (ResourceInfo resourceInfo, FeatureContext context) {
+        ResourceFilters filtersAnnotation = resourceInfo.getResourceMethod().getAnnotation(ResourceFilters.class);
+        if (filtersAnnotation == null)
+            filtersAnnotation = resourceInfo.getResourceClass().getAnnotation(ResourceFilters.class);
+
+        if (filtersAnnotation != null) {
+            for (Class<?> filter : filtersAnnotation.value())
+                context.register(filter);
+        }
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/AdminController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/AdminController.java
index 97dbbcd..f3dac49 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/AdminController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/AdminController.java
@@ -17,7 +17,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.auditing.AuditRecord;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
index f41d4f8..e061644 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
@@ -27,7 +27,7 @@
 import org.springframework.stereotype.Controller;
 
 import org.glassfish.jersey.server.ContainerRequest;
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.authentication.AuthenticationManager;
 import de.ids_mannheim.korap.authentication.http.AuthorizationData;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
index 7935098..1ab9d06 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
@@ -13,7 +13,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index 8ea0f37..0efcbbe 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -27,7 +27,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
index a1e9fa7..685bb3f 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
@@ -30,7 +30,7 @@
 import com.nimbusds.oauth2.sdk.TokenRequest;
 import com.nimbusds.oauth2.sdk.http.HTTPRequest.Method;
 import com.nimbusds.oauth2.sdk.id.State;
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index 8ffc164..7e4b75e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -18,7 +18,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.dto.InstalledPluginDto;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java
index 581b325..d377da3 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java
@@ -15,7 +15,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.dto.InstalledPluginDto;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/QueryReferenceController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/QueryReferenceController.java
index 36ed830..012bc97 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/QueryReferenceController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/QueryReferenceController.java
@@ -19,7 +19,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.constant.QueryType;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java
index bee5a3d..9e01afe 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java
@@ -10,7 +10,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.dto.ResourceDto;
 import de.ids_mannheim.korap.service.ResourceService;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/ShibbolethUserController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/ShibbolethUserController.java
index 79ba584..ce7ba2b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/ShibbolethUserController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/ShibbolethUserController.java
@@ -28,7 +28,7 @@
 import org.springframework.stereotype.Controller;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.authentication.AuthenticationManager;
 import de.ids_mannheim.korap.config.Attributes;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index 415d98d..efab410 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -21,7 +21,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.constant.UserGroupStatus;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java
index a4d7303..3ff57a7 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserSettingController.java
@@ -17,7 +17,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
index bc3c06f..a0019cf 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
@@ -21,7 +21,7 @@
 import org.springframework.stereotype.Controller;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.constant.QueryType;