Added authentication to metadata controller (issue #38) & updated search
krill error handling

Change-Id: I2937de0223561246c3af078e9ada1258e4fae7d2
diff --git a/core/Changes b/core/Changes
index 3f2d0b7..378a85e 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,7 +1,8 @@
 # version 0.61.6
-
 06/02/2018
    - Added default foundry for structure layer (margaretha)
+   - Added authentication to metadata controller (margaretha, issue #38)
+   - Updated search krill error handling (margaretha)
 
 # version 0.61.5
 17/12/2018
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index 72b8cfc..446137c 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -111,7 +111,8 @@
     public static final int STATUS_OK = 1000;
     public static final int NOTHING_CHANGED = 1001;
     public static final int REQUEST_INVALID = 1002;
-    public static final int ACCESS_DENIED = 1003;
+    
+//    public static final int ACCESS_DENIED = 1003;
 
     
     // User group and member 
diff --git a/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java b/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
index fa6a680..ca63c08 100644
--- a/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
+++ b/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
@@ -193,14 +193,7 @@
 
     }
 
-    public String retrieveMatchInfo (String corpusId, String docId,
-            String textId, String matchId, Set<String> foundries,
-            String username, HttpHeaders headers, Set<String> layers,
-            boolean spans, boolean highlights) throws KustvaktException {
-        String matchid =
-                searchKrill.getMatchId(corpusId, docId, textId, matchId);
-
-        User user = createUser(username, headers);
+    private Pattern determineAvailabilityPattern (User user) {
         Pattern p = null;
         if (user != null) {
             CorpusAccess corpusAccess = user.getCorpusAccess();
@@ -216,9 +209,22 @@
                     break;
             }
         }
+        return p;
+    }
+    
+    public String retrieveMatchInfo (String corpusId, String docId,
+            String textId, String matchId, Set<String> foundries,
+            String username, HttpHeaders headers, Set<String> layers,
+            boolean spans, boolean highlights) throws KustvaktException {
+        String matchid =
+                searchKrill.getMatchId(corpusId, docId, textId, matchId);
+
+        User user = createUser(username, headers);
+        Pattern p = determineAvailabilityPattern(user);
+        
         boolean match_only = foundries == null || foundries.isEmpty();
         String results;
-        try {
+//        try {
             if (!match_only) {
 
                 ArrayList<String> foundryList = new ArrayList<String>();
@@ -241,12 +247,12 @@
             else {
                 results = searchKrill.getMatch(matchid, p);
             }
-        }
-        catch (Exception e) {
-            jlog.error("Exception in the MatchInfo service encountered!", e);
-            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
-                    e.getMessage());
-        }
+//        }
+//        catch (Exception e) {
+//            jlog.error("Exception in the MatchInfo service encountered!", e);
+//            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+//                    e.getMessage());
+//        }
         if (DEBUG){
             jlog.debug("MatchInfo results: " + results);
         }
@@ -254,9 +260,12 @@
     }
 
     public String retrieveDocMetadata (String corpusId, String docId,
-            String textId) {
+            String textId, String username, HttpHeaders headers)
+            throws KustvaktException {
+        User user = createUser(username, headers);
+        Pattern p = determineAvailabilityPattern(user);
         String textSigle = searchKrill.getTextSigle(corpusId, docId, textId);
-        return searchKrill.getFields(textSigle);
+        return searchKrill.getFields(textSigle, p);
     }
 
     public String getCollocationBase (String query) throws KustvaktException {
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java b/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java
index f81b5aa..0580d7e 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java
@@ -29,8 +29,7 @@
  * @author Nils Diewald
  */
 public class SearchKrill {
-    private final static Logger jlog = LogManager
-            .getLogger(SearchKrill.class);
+    private final static Logger jlog = LogManager.getLogger(SearchKrill.class);
 
     private static final boolean DEBUG = false;
 
@@ -39,13 +38,13 @@
     String i = "/Users/hanl/Projects/prep_corpus";
     String klinux10 = "/vol/work/hanl/indices";
     public static KrillIndex index;
-    
+
     /**
      * Constructor
      */
     // todo: use korap.config to get index location
     public SearchKrill (String path) {
-    	
+
         try {
             if (path.equals(":temp:")) {
                 index = new KrillIndex();
@@ -61,7 +60,7 @@
             };
         }
         catch (IOException e) {
-            jlog.error("Unable to loadSubTypes index:"+ e.getMessage());
+            jlog.error("Unable to loadSubTypes index:" + e.getMessage());
         };
     };
 
@@ -69,7 +68,6 @@
         return index;
     };
 
-
     /**
      * Search in the Lucene index.
      * 
@@ -78,12 +76,12 @@
      *            filters.
      */
     public String search (String json) {
-        if (DEBUG){
+        if (DEBUG) {
             jlog.debug(json);
         }
-        if (index != null){
+        if (index != null) {
             String result = new Krill(json).apply(index).toJsonString();
-            if (DEBUG){
+            if (DEBUG) {
                 jlog.debug(result);
             }
             return result;
@@ -93,7 +91,6 @@
         return kr.toJsonString();
     };
 
-
     /**
      * Search in the Lucene index and return matches as token lists.
      * 
@@ -103,7 +100,7 @@
      */
     @Deprecated
     public String searchTokenList (String json) {
-        if (DEBUG){
+        if (DEBUG) {
             jlog.debug(json);
         }
         if (index != null)
@@ -113,116 +110,106 @@
         return kr.toJsonString();
     };
 
-
     /**
      * Get info on a match - by means of a richly annotated html
      * snippet.
      * 
      * @param id
      *            match id
-     * @param availabilityList 
-     * @throws KustvaktException 
+     * @param availabilityList
+     * @throws KustvaktException
      */
-    public String getMatch (String id, Pattern licensePattern) {
-    	Match km;
+    public String getMatch (String id, Pattern licensePattern) throws KustvaktException {
+        Match km;
         if (index != null) {
             try {
-            	km = index.getMatch(id);
-            	String availability = km.getAvailability();
-            	if (licensePattern!=null && availability != null){
-            		Matcher m = licensePattern.matcher(availability);
-            		if (!m.matches()){
-            		    if (DEBUG){
-            		        jlog.debug("availability: "+availability);
-            		    }
-                        if (availability.isEmpty()){
-                            km.addError(StatusCodes.MISSING_ATTRIBUTE, 
-                                    "Availability for "+ id +"is empty.", id);
-                        }
-            			km = new Match();
-            			km.addError(StatusCodes.ACCESS_DENIED, 
-            				"Retrieving match info with ID "+id+" is not allowed.", id);
-            		}
-            	}
+                km = index.getMatch(id);
+                String availability = km.getAvailability();
+                checkAvailability(licensePattern, availability, id);
             }
             catch (QueryException qe) {
                 km = new Match();
                 km.addError(qe.getErrorCode(), qe.getMessage());
             }
         }
-        else{
-        	km = new Match();
-        	km.addError(601, "Unable to find index");
+        else {
+            km = new Match();
+            km.addError(601, "Unable to find index");
         }
         return km.toJsonString();
     };
 
+    private void checkAvailability (Pattern licensePattern, String availability,
+            String id) throws KustvaktException {
+        if (DEBUG) {
+            jlog.debug("pattern: " + licensePattern.toString()
+                    + ", availability: " + availability);
+        }
+        if (licensePattern != null && availability != null) {
+            Matcher m = licensePattern.matcher(availability);
+            if (!m.matches()) {
+                if (availability.isEmpty()) {
+                    throw new KustvaktException(StatusCodes.MISSING_ATTRIBUTE,
+                            "Availability for " + id + "is empty.", id);
+                }
+                throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                        "Retrieving resource with ID " + id
+                                + " is not allowed.",
+                        id);
+            }
+        }
 
-	/*
-	 * Retrieve the meta fields for a certain document
-	 */
-	public String getFields (String id) {
-		MetaFields meta;
+    }
+    
+    /*
+     * Retrieve the meta fields for a certain document
+     */
+    public String getFields (String id, Pattern licensePattern)
+            throws KustvaktException {
+        MetaFields meta;
 
-		// No index found
-		if (index == null) {
-        	meta = new MetaFields(id);
-        	meta.addError(601, "Unable to find index");
-		}
+        // No index found
+        if (index == null) {
+            meta = new MetaFields(id);
+            meta.addError(601, "Unable to find index");
+        }
 
-		// Index available
-		else {
+        // Index available
+        else {
+            // Get fields
+            meta = index.getFields(id);
+        };
 
-			//Get fields
-			meta = index.getFields(id);
-		};
-		return meta.toJsonString();
-	};
+        String availability = meta.getFieldValue("availability");
+        checkAvailability(licensePattern, availability, id);
 
+        return meta.toJsonString();
+    };
 
-	
     public String getMatch (String id, List<String> foundries,
             List<String> layers, boolean includeSpans,
-            boolean includeHighlights, boolean sentenceExpansion, 
-            Pattern licensePattern) {
-    	 Match km;
+            boolean includeHighlights, boolean sentenceExpansion,
+            Pattern licensePattern) throws KustvaktException {
+        Match km;
         if (index != null) {
             try {
-            	km = index.getMatchInfo(id, "tokens", true, foundries,
-                        layers, includeSpans, includeHighlights,
-                        sentenceExpansion);
-            	String availability = km.getAvailability();
-            	
-            	if (licensePattern !=null && availability != null){
-            	    if (availability.isEmpty()){
-            	        km.addError(StatusCodes.MISSING_ATTRIBUTE, 
-                                "Availability for "+ id +"is empty.", id);
-            	    }
-            		Matcher m = licensePattern.matcher(availability);
-            		if (!m.matches()){
-            		    if (DEBUG){
-            		        jlog.debug("pattern: "+ licensePattern.toString() + ", availability: "+availability);
-            		    }
-            			km = new Match();
-            			km.addError(StatusCodes.ACCESS_DENIED, 
-            					"Retrieving match info with ID "+id+" is not allowed.", id);
-            		}
-            	}
-            	
+                km = index.getMatchInfo(id, "tokens", true, foundries, layers,
+                        includeSpans, includeHighlights, sentenceExpansion);
+                String availability = km.getAvailability();
+                checkAvailability(licensePattern, availability, id);
             }
             catch (QueryException qe) {
                 km = new Match();
                 km.addError(qe.getErrorCode(), qe.getMessage());
             }
         }
-        else{
-        	km = new Match();
-        	km.addError(601, "Unable to find index");
+        else {
+            km = new Match();
+            km.addError(601, "Unable to find index");
         }
         return km.toJsonString();
     };
 
-
     /**
      * Get info on a match - by means of a richly annotated html
      * snippet.
@@ -270,7 +257,6 @@
         return km.toJsonString();
     };
 
-
     /**
      * Get statistics on (virtual) collections.
      * 
@@ -284,23 +270,23 @@
             return "{\"documents\" : -1, error\" : \"No index given\" }";
         };
 
-		// Define a virtual corpus
-		KrillCollection kc;
-		if (json != null && !json.equals("")) {
-			if (DEBUG){
-			    jlog.debug(json);
-			}
+        // Define a virtual corpus
+        KrillCollection kc;
+        if (json != null && !json.equals("")) {
+            if (DEBUG) {
+                jlog.debug(json);
+            }
 
-			// Create Virtual collection from json search
-			kc = new KrillCollection(json);
-		}
+            // Create Virtual collection from json search
+            kc = new KrillCollection(json);
+        }
 
-		// There is no json string defined
-		else {
+        // There is no json string defined
+        else {
 
-			// Create Virtual collection of everything
-			kc = new KrillCollection();
-		};
+            // Create Virtual collection of everything
+            kc = new KrillCollection();
+        };
 
         // Set index
         kc.setIndex(index);
@@ -308,11 +294,11 @@
         // Get numbers from index (currently slow)
         try {
             docs = kc.numberOf("documents");
-			if (docs > 0) {
-				tokens = kc.numberOf("tokens");
-				sentences = kc.numberOf("base/sentences");
-				paragraphs = kc.numberOf("base/paragraphs");
-			};
+            if (docs > 0) {
+                tokens = kc.numberOf("tokens");
+                sentences = kc.numberOf("base/sentences");
+                paragraphs = kc.numberOf("base/paragraphs");
+            };
         }
         catch (IOException e) {
             e.printStackTrace();
@@ -325,7 +311,6 @@
         return sb.toString();
     };
 
-
     /**
      * Return the match identifier as a string.
      * This is a convenient method to deal with legacy instantiation
@@ -334,22 +319,21 @@
      */
     public String getMatchId (String corpusID, String docID, String textID,
             String matchID) {
-        // Create a string representation of the match 
+        // Create a string representation of the match
         StringBuilder sb = new StringBuilder();
         sb.append("match-").append(corpusID).append('/').append(docID)
                 .append('/').append(textID).append('-').append(matchID);
         return sb.toString();
     };
 
-
     /**
      * Return the text sigle as a string.
      */
     public String getTextSigle (String corpusID, String docID, String textID) {
-        // Create a string representation of the match 
+        // Create a string representation of the match
         StringBuilder sb = new StringBuilder();
-        sb.append(corpusID).append('/').append(docID)
-                .append('/').append(textID);
+        sb.append(corpusID).append('/').append(docID).append('/')
+                .append(textID);
         return sb.toString();
     };
 };
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 0cd61a3..f3299a8 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
@@ -163,6 +163,8 @@
         return Response.ok(result).build();
     }
 
+    // EM: legacy support
+    @Deprecated
     @GET
     @Path("{version}/corpus/{corpusId}/{docId}/{textId}/{matchId}/matchInfo")
     public Response getMatchInfo (@Context SecurityContext ctx,
@@ -177,16 +179,40 @@
             // Highlights may also be a list of valid highlight classes
             @QueryParam("hls") Boolean highlights) throws KustvaktException {
 
+        return retrieveMatchInfo(ctx, headers, locale, corpusId, docId, textId,
+                matchId, foundries, layers, spans, highlights);
+    }
+    
+    @GET
+    @Path("{version}/corpus/{corpusId}/{docId}/{textId}/{matchId}")
+    public Response retrieveMatchInfo (@Context SecurityContext ctx,
+            @Context HttpHeaders headers, @Context Locale locale,
+            @PathParam("corpusId") String corpusId,
+            @PathParam("docId") String docId,
+            @PathParam("textId") String textId,
+            @PathParam("matchId") String matchId,
+            @QueryParam("foundry") Set<String> foundries,
+            @QueryParam("layer") Set<String> layers,
+            @QueryParam("spans") Boolean spans, 
+            // Highlights may also be a list of valid highlight classes
+            @QueryParam("hls") Boolean highlights) throws KustvaktException {
+
         TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
         scopeService.verifyScope(tokenContext, OAuth2Scope.MATCH_INFO);
         spans = spans != null ? spans : false;
         highlights = highlights != null ? highlights : false;
         if (layers == null || layers.isEmpty()) layers = new HashSet<>();
 
-        String results = searchService.retrieveMatchInfo(corpusId, docId,
-                textId, matchId, foundries, tokenContext.getUsername(), headers,
-                layers, spans, highlights);
-        return Response.ok(results).build();
+        try{
+            String results = searchService.retrieveMatchInfo(corpusId, docId,
+                    textId, matchId, foundries, tokenContext.getUsername(),
+                    headers, layers, spans, highlights);
+            return Response.ok(results).build();
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
+        }
+        
     }
 
     /*
@@ -197,12 +223,21 @@
     @GET
     @Path("{version}/corpus/{corpusId}/{docId}/{textId}")
     public Response getMetadata (@PathParam("corpusId") String corpusId,
-            @PathParam("docId") String docId, @PathParam("textId") String textId
-    // @QueryParam("fields") Set<String> fields
+            @PathParam("docId") String docId,
+            @PathParam("textId") String textId, 
+            @Context SecurityContext ctx,
+            @Context HttpHeaders headers
+            // @QueryParam("fields") Set<String> fields
     ) throws KustvaktException {
-        String results =
-                searchService.retrieveDocMetadata(corpusId, docId, textId);
-        return Response.ok(results).build();
+        TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
+        try {
+            String results = searchService.retrieveDocMetadata(corpusId, docId,
+                    textId, tokenContext.getUsername(), headers);
+            return Response.ok(results).build();
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
+        }
     }
 
     @POST