Implemented configurable resource filters for search and match info APIs

Added tests and settings for ICC scenario.

Change-Id: If22b96cd12f2a39c134a45f9e3e4b2da8bcd36dc
diff --git a/core/Changes b/core/Changes
index cd92fca..3590c21 100644
--- a/core/Changes
+++ b/core/Changes
@@ -2,6 +2,7 @@
 
 - Support token array in matchinfo (fixes #570; diewald)
 - Added user info web-service (solved #566)
+- Implemented configurable resource filters for search and match info APIs
 
 # version 0.69.3
 
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 3cc768b..7c180e8 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
@@ -29,7 +29,7 @@
 import org.springframework.stereotype.Controller;
 
 import de.ids_mannheim.korap.web.utils.ResourceFilters;
-
+import de.ids_mannheim.korap.web.utils.SearchResourceFilters;
 import de.ids_mannheim.korap.config.KustvaktConfiguration;
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -154,6 +154,7 @@
     @POST
     @Path("{version}/search")
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    @SearchResourceFilters
     public Response searchPost (@Context SecurityContext context,
             @Context Locale locale, 
             @Context HttpHeaders headers,
@@ -212,6 +213,7 @@
     @GET
     @Path("{version}/search")
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    @SearchResourceFilters
     public Response searchGet (@Context SecurityContext securityContext,
             @Context HttpServletRequest request,
             @Context HttpHeaders headers, @Context Locale locale,
@@ -251,6 +253,7 @@
     @GET
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
     @Path("{version}/corpus/{corpusId}/{docId}/{textId}/{matchId}/matchInfo")
+    @SearchResourceFilters
     public Response getMatchInfo (@Context SecurityContext ctx,
             @Context HttpHeaders headers, @Context Locale locale,
             @PathParam("corpusId") String corpusId,
@@ -271,6 +274,7 @@
     @GET
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
     @Path("{version}/corpus/{corpusId}/{docId}/{textId}/{matchId}")
+    @SearchResourceFilters
     public Response retrieveMatchInfo (@Context SecurityContext ctx,
             @Context HttpHeaders headers, @Context Locale locale,
             @PathParam("corpusId") String corpusId,
@@ -286,37 +290,45 @@
             // Highlights may also be a list of valid highlight classes
             @QueryParam("hls") Boolean highlights) throws KustvaktException {
 
-        Boolean expandToSentence = true;
-        if (expansion != null && (expansion.equals("false") || expansion.equals("null"))) {
-            expandToSentence = false;
+        TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
+        try {
+            scopeService.verifyScope(tokenContext, OAuth2Scope.MATCH_INFO);
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
         }
 
-        TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
-        scopeService.verifyScope(tokenContext, OAuth2Scope.MATCH_INFO);
+        Boolean expandToSentence = true;
+        if (expansion != null
+                && (expansion.equals("false") || expansion.equals("null"))) {
+            expandToSentence = false;
+        }
         spans = spans != null ? spans : false;
         Boolean snippet = true;
         Boolean tokens = false;
-        if (snippetStr != null && (snippetStr.equals("false") || snippetStr.equals("null")))
+        if (snippetStr != null
+                && (snippetStr.equals("false") || snippetStr.equals("null")))
             snippet = false;
 
-        if (tokensStr != null && (tokensStr.equals("true") || tokensStr.equals("1") || tokensStr.equals("yes")))
+        if (tokensStr != null && (tokensStr.equals("true")
+                || tokensStr.equals("1") || tokensStr.equals("yes")))
             tokens = true;
 
         highlights = highlights != null ? highlights : false;
-        if (layers == null || layers.isEmpty()) layers = new HashSet<>();
+        if (layers == null || layers.isEmpty())
+            layers = new HashSet<>();
 
-        try{
-            String results = searchService.retrieveMatchInfo(
-                corpusId, docId,
-                textId, matchId, true, foundries, tokenContext.getUsername(),
-                headers, layers, spans, snippet, tokens,
-                expandToSentence, highlights);
+        try {
+            String results = searchService.retrieveMatchInfo(corpusId, docId,
+                    textId, matchId, true, foundries,
+                    tokenContext.getUsername(), headers, layers, spans, snippet,
+                    tokens, expandToSentence, highlights);
             return Response.ok(results).build();
         }
         catch (KustvaktException e) {
             throw kustvaktResponseHandler.throwit(e);
         }
-        
+
     }
 
     /*
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/utils/SearchResourceFilters.java b/core/src/main/java/de/ids_mannheim/korap/web/utils/SearchResourceFilters.java
new file mode 100644
index 0000000..12dbf57
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/web/utils/SearchResourceFilters.java
@@ -0,0 +1,22 @@
+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 SearchResourceFilters {
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/utils/SearchResourceFiltersFeature.java b/core/src/main/java/de/ids_mannheim/korap/web/utils/SearchResourceFiltersFeature.java
new file mode 100644
index 0000000..45a7c4e
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/web/utils/SearchResourceFiltersFeature.java
@@ -0,0 +1,58 @@
+package de.ids_mannheim.korap.web.utils;
+
+import java.util.List;
+
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.Provider;
+
+import org.glassfish.jersey.model.internal.CommonConfig;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import de.ids_mannheim.korap.web.filter.APIVersionFilter;
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+/**
+ * Registers {@link javax.ws.rs.container.ContainerRequestFilter}
+ * and {@link javax.ws.rs.container.ContainerResponseFilter}
+ * classes for a resource method annotated with {@link ResourceFilters}.
+ */
+@Provider
+@Component
+public class SearchResourceFiltersFeature implements DynamicFeature {
+
+    @Value("${search.resource.filters:AuthenticationFilter,DemoUserFilter}")
+    private String[] resourceFilters;
+    
+    @Override
+    public void configure (ResourceInfo resourceInfo, FeatureContext context) {
+        SearchResourceFilters filters = resourceInfo.getResourceMethod()
+                .getAnnotation(SearchResourceFilters.class);
+        if (filters != null) {
+            CommonConfig con = (CommonConfig) context.getConfiguration();
+            con.getComponentBag().clear();
+        }        
+        else {
+            filters = resourceInfo.getResourceClass()
+            .getAnnotation(SearchResourceFilters.class);
+        }
+        
+        if (filters != null) {
+            List<?> list = Arrays.asList(resourceFilters);
+            if (!list.contains("APIVersionFilter")) {
+                context.register(APIVersionFilter.class);
+            }
+            
+             for(String c : resourceFilters) {
+                    try {
+                        context.register(Class.forName("de.ids_mannheim.korap.web.filter." + c));
+                    }
+                    catch (ClassNotFoundException e) {
+                        e.printStackTrace();
+                    }
+            }
+        }
+    }
+}