Implemented DTO constructions for the annotation description service.

Change-Id: I3b68d6525106dd449ca38e63448973b65ced3cfc
diff --git a/src/main/java/de/ids_mannheim/korap/dao/AnnotationDao.java b/src/main/java/de/ids_mannheim/korap/dao/AnnotationDao.java
index a6394aa..366546e 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/AnnotationDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/AnnotationDao.java
@@ -10,8 +10,6 @@
 import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 
 import de.ids_mannheim.korap.entity.AnnotationPair;
@@ -27,7 +25,6 @@
 @Component
 public class AnnotationDao {
 
-    private static Logger jlog = LoggerFactory.getLogger(AnnotationDao.class);
     @PersistenceContext
     private EntityManager entityManager;
 
@@ -56,20 +53,29 @@
      * layer in the given foundry. If foundry is empty, retrieves data
      * for all foundry and layer pairs.
      * 
-     * @param foundry a foundry code
-     * @param layer a layer code
+     * @param foundry
+     *            a foundry code
+     * @param layer
+     *            a layer code
      * @return a list of foundry-layer pairs.
      */
     public List<AnnotationPair> getAnnotationDescriptions (String foundry,
             String layer) {
-        
+
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
         CriteriaQuery<Object> query = criteriaBuilder.createQuery();
         Root<AnnotationPair> annotationPair = query.from(AnnotationPair.class);
         annotationPair.fetch("annotation1");
         annotationPair.fetch("annotation2");
         annotationPair.fetch("values");
+
+        // EM: Hibernate bug in join n:m (see AnnotationPair.values). 
+        // There should not be any redundant AnnotationPair. 
+        // The redundancy can be alsp avoided with fetch=FetchType.EAGER 
+        // because Hibernate does 2 selects.  
+        query.distinct(true);
         query = query.select(annotationPair);
+
         if (!foundry.isEmpty()) {
             Predicate foundryPredicate = criteriaBuilder.equal(
                     annotationPair.get("annotation1").get("code"), foundry);
@@ -85,7 +91,6 @@
             }
         }
 
-        query.distinct(true); 
         Query q = entityManager.createQuery(query);
         return q.getResultList();
     }
diff --git a/src/main/java/de/ids_mannheim/korap/dao/ResourceDao.java b/src/main/java/de/ids_mannheim/korap/dao/ResourceDao.java
index 0dd68f8..0a14a3c 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/ResourceDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/ResourceDao.java
@@ -9,8 +9,6 @@
 import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.Root;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 
 import de.ids_mannheim.korap.entity.Resource;
@@ -24,8 +22,6 @@
 @Component
 public class ResourceDao {
 
-//    private static Logger jlog = LoggerFactory.getLogger(ResourceDao.class);
-
     @PersistenceContext
     private EntityManager entityManager;
     
diff --git a/src/main/java/de/ids_mannheim/korap/dto/FoundryDto.java b/src/main/java/de/ids_mannheim/korap/dto/FoundryDto.java
new file mode 100644
index 0000000..4d3ea6d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/dto/FoundryDto.java
@@ -0,0 +1,32 @@
+package de.ids_mannheim.korap.dto;
+
+import java.util.List;
+import java.util.Map;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Data transfer object for annotation descriptions (e.g. for
+ * Kalamar).
+ * 
+ * @author margaretha
+ * 
+ */
+@Getter
+@Setter
+public class FoundryDto {
+
+    private String code;
+    private String description;
+    private List<Layer> layers;
+
+    @Getter
+    @Setter
+    public class Layer {
+        private String code;
+        private String description;
+        // EM: pairs of annotation values and their description
+        private Map<String, String> tags;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/dto/LayerDto.java b/src/main/java/de/ids_mannheim/korap/dto/LayerDto.java
index 4a6c0c7..b9884d8 100644
--- a/src/main/java/de/ids_mannheim/korap/dto/LayerDto.java
+++ b/src/main/java/de/ids_mannheim/korap/dto/LayerDto.java
@@ -3,6 +3,12 @@
 import lombok.Getter;
 import lombok.Setter;
 
+/**
+ * Data transfer object for layer description (e.g. for KorapSRU).
+ * 
+ * @author margaretha
+ *
+ */
 @Getter
 @Setter
 public class LayerDto {
diff --git a/src/main/java/de/ids_mannheim/korap/dto/ResourceDto.java b/src/main/java/de/ids_mannheim/korap/dto/ResourceDto.java
index 45575df..3a14423 100644
--- a/src/main/java/de/ids_mannheim/korap/dto/ResourceDto.java
+++ b/src/main/java/de/ids_mannheim/korap/dto/ResourceDto.java
@@ -5,6 +5,11 @@
 import lombok.Getter;
 import lombok.Setter;
 
+/** Data transfer object for resource description (e.g. for KorapSRU). 
+ * 
+ * @author margaretha
+ *
+ */
 @Setter
 @Getter
 public class ResourceDto {
diff --git a/src/main/java/de/ids_mannheim/korap/dto/converter/AnnotationConverter.java b/src/main/java/de/ids_mannheim/korap/dto/converter/AnnotationConverter.java
index 61a6f63..c049c2a 100644
--- a/src/main/java/de/ids_mannheim/korap/dto/converter/AnnotationConverter.java
+++ b/src/main/java/de/ids_mannheim/korap/dto/converter/AnnotationConverter.java
@@ -1,34 +1,139 @@
 package de.ids_mannheim.korap.dto.converter;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.springframework.stereotype.Component;
 
+import de.ids_mannheim.korap.dto.FoundryDto;
+import de.ids_mannheim.korap.dto.FoundryDto.Layer;
 import de.ids_mannheim.korap.dto.LayerDto;
+import de.ids_mannheim.korap.entity.Annotation;
 import de.ids_mannheim.korap.entity.AnnotationPair;
 
+/**
+ * AnnotationConverter prepares data transfer objects (DTOs) from
+ * entities. The DTOs, for instance, are serialized into JSON in the
+ * controller classes and then sent as the response entity.
+ * 
+ * @author margaretha
+ *
+ */
 @Component
 public class AnnotationConverter {
 
-    public List<LayerDto> convertToLayerDto (List<AnnotationPair> layers) {
-        List<LayerDto> layerDtos = new ArrayList<LayerDto>(layers.size());
+    /**
+     * Returns layer descriptions in a list of {@link LayerDto}s.
+     * 
+     * @param pairs
+     *            a list of {@link AnnotationPair}s
+     * @return a list of {@link LayerDto}s
+     */
+    public List<LayerDto> convertToLayerDto (List<AnnotationPair> pairs) {
+        List<LayerDto> layerDtos = new ArrayList<LayerDto>(pairs.size());
         LayerDto dto;
         String foundry, layer;
-        for (AnnotationPair l : layers) {
+        for (AnnotationPair p : pairs) {
             dto = new LayerDto();
-            dto.setId(l.getId());
-            dto.setDescription(l.getEnglishDescription());
+            dto.setId(p.getId());
+            dto.setDescription(p.getDescription());
 
-            foundry = l.getAnnotation1().getCode();
+            foundry = p.getAnnotation1().getCode();
             dto.setFoundry(foundry);
 
-            layer = l.getAnnotation2().getCode();
+            layer = p.getAnnotation2().getCode();
             dto.setLayer(layer);
             dto.setCode(foundry + "/" + layer);
             layerDtos.add(dto);
         }
-        
+
         return layerDtos;
     }
+
+
+    /**
+     * Returns foundry description in {@link FoundryDto}s
+     * 
+     * @param pairs
+     *            a list of {@link AnnotationPair}s
+     * @param language
+     * @return a list of {@link FoundryDto}s
+     */
+    public List<FoundryDto> convertToFoundryDto (List<AnnotationPair> pairs,
+            String language) {
+        List<FoundryDto> foundryDtos = new ArrayList<FoundryDto>(pairs.size());
+        Map<String, List<AnnotationPair>> foundryMap = createFoundryMap(pairs);
+
+        for (String key : foundryMap.keySet()) {
+            List<AnnotationPair> foundries = foundryMap.get(key);
+            List<Layer> layers = new ArrayList<Layer>(foundries.size());
+            FoundryDto dto = null;
+
+            for (AnnotationPair f : foundries) {
+                if (dto == null) {
+                    Annotation foundry = f.getAnnotation1();
+                    dto = new FoundryDto();
+                    if (language.equals("de")){
+                        dto.setDescription(foundry.getGermanDescription());
+                    }
+                    else{
+                        dto.setDescription(foundry.getDescription());
+                    }
+                    dto.setCode(foundry.getCode());
+                }
+
+                Annotation layer = f.getAnnotation2();
+                Map<String, String> tags = new HashMap<>();
+                for (Annotation value : f.getValues()) {
+                    if (language.equals("de")){
+                        tags.put(value.getCode(), value.getGermanDescription());
+                    }
+                    else{
+                        tags.put(value.getCode(), value.getDescription());
+                    }
+                }
+
+                Layer l = dto.new Layer();
+                l.setCode(layer.getCode());
+                
+                if (language.equals("de")){
+                    l.setDescription(layer.getGermanDescription());
+                }
+                else{
+                    l.setDescription(layer.getDescription());
+                }
+                
+                l.setTags(tags);
+                layers.add(l);
+            }
+
+            dto.setLayers(layers);
+            foundryDtos.add(dto);
+        }
+
+        return foundryDtos;
+    }
+
+
+    private Map<String, List<AnnotationPair>> createFoundryMap (
+            List<AnnotationPair> pairs) {
+        Map<String, List<AnnotationPair>> foundries =
+                new HashMap<String, List<AnnotationPair>>();
+        for (AnnotationPair p : pairs) {
+            String foundryCode = p.getAnnotation1().getCode();
+            if (foundries.containsKey(foundryCode)) {
+                foundries.get(foundryCode).add(p);
+            }
+            else {
+                List<AnnotationPair> foundryList =
+                        new ArrayList<AnnotationPair>();
+                foundryList.add(p);
+                foundries.put(foundryCode, foundryList);
+            }
+        }
+
+        return foundries;
+    }
 }
diff --git a/src/main/java/de/ids_mannheim/korap/entity/Annotation.java b/src/main/java/de/ids_mannheim/korap/entity/Annotation.java
index e63eb74..1fbe3fb 100644
--- a/src/main/java/de/ids_mannheim/korap/entity/Annotation.java
+++ b/src/main/java/de/ids_mannheim/korap/entity/Annotation.java
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.entity;
 
+import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
@@ -21,10 +22,14 @@
     private String code;
     private String type;
     private String description;
+    @Column(name = "de_description")
+    private String germanDescription;
+
 
     @Override
     public String toString () {
         return "id=" + id + ", code= " + code + ", type= " + type
-                + ", description=" + description;
+                + ", description=" + description + ", germanDescription="
+                + germanDescription;
     }
 }
diff --git a/src/main/java/de/ids_mannheim/korap/entity/AnnotationPair.java b/src/main/java/de/ids_mannheim/korap/entity/AnnotationPair.java
index ee69da0..ca580c3 100644
--- a/src/main/java/de/ids_mannheim/korap/entity/AnnotationPair.java
+++ b/src/main/java/de/ids_mannheim/korap/entity/AnnotationPair.java
@@ -11,8 +11,9 @@
 import javax.persistence.JoinColumn;
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
-import javax.persistence.OneToOne;
+import javax.persistence.ManyToOne;
 import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
 
 import org.hibernate.annotations.Fetch;
 import org.hibernate.annotations.FetchMode;
@@ -23,7 +24,8 @@
 @Setter
 @Getter
 @Entity
-@Table(name = "annotation_pair")
+@Table(name = "annotation_pair", 
+    uniqueConstraints=@UniqueConstraint(columnNames={"annotation1","annotation2"}))
 public class AnnotationPair {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -32,34 +34,33 @@
     private int annotationId1;
     @Column(name = "annotation2")
     private int annotationId2;
-    @Column(name = "de_description")
-    private String germanDescription;
     @Column(name = "description")
-    private String englishDescription;
+    private String description;
 
     @Fetch(FetchMode.SELECT)
-    @OneToOne
+    @ManyToOne //(fetch=FetchType.LAZY) 
     @JoinColumn(name = "annotation1", insertable = false, updatable = false)
     private Annotation annotation1;
     
     @Fetch(FetchMode.SELECT)
-    @OneToOne
+    @ManyToOne //(fetch=FetchType.LAZY) 
     @JoinColumn(name = "annotation2", insertable = false, updatable = false)
     private Annotation annotation2;
 
-    @ManyToMany(fetch=FetchType.LAZY)
+    @ManyToMany(fetch=FetchType.LAZY) //(fetch=FetchType.EAGER)
     @JoinTable(
             name="annotation_pair_value",
             joinColumns=@JoinColumn(name="pair_id", referencedColumnName="id"),
-            inverseJoinColumns=@JoinColumn(name="value_id", referencedColumnName="id")
+            inverseJoinColumns=@JoinColumn(name="value_id", referencedColumnName="id"),
+            uniqueConstraints = @UniqueConstraint(columnNames = {
+                            "pair_id", "value_id" })
     )
     private Set<Annotation> values;
 
     @Override
     public String toString () {
         return "id=" + id + ", annotation1=" + annotationId1 + ", annotation2="
-                + annotationId1 + ", description=" + englishDescription
-                + ", germanDescription= " + germanDescription 
+                + annotationId1 + ", description=" + description
                 + ", annotation1= "+ annotation1
                 + ", annotation2= "+ annotation2;
                 
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index 982026e..889f9f5 100644
--- a/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -1,12 +1,9 @@
 package de.ids_mannheim.korap.exceptions;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import de.ids_mannheim.korap.config.ConfigLoader;
-
-import java.io.IOException;
 import java.util.Properties;
 
+import de.ids_mannheim.korap.config.ConfigLoader;
+
 /**
  * @author hanl
  * @date 07/09/2014
@@ -23,16 +20,18 @@
     public static final int ILLEGAL_ARGUMENT = 104;
     public static final int MISSING_ARGUMENT = 105;
     public static final int CONNECTION_ERROR = 106;
-    public static final int PARAMETER_INVALID = 107;
+    public static final int INVALID_ARGUMENT = 107;
     public static final int NOT_SUPPORTED = 108;
-
+    
     /**
      * 300 status codes for query language and serialization
      */
 
     public static final int NO_QUERY = 301;
     public static final int SERIALIZATION_FAILED = 302;
-
+    public static final int MISSING_ATTRIBUTE = 303;
+    public static final int INVALID_ATTRIBUTE = 304;
+    public static final int UNSUPPORTED_VALUE = 305;
 
     /**
      *  400 status codes for authorization and rewrite functions
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/full/AnnotationService.java b/src/main/java/de/ids_mannheim/korap/web/service/full/AnnotationService.java
index 93afb41..917baa4 100644
--- a/src/main/java/de/ids_mannheim/korap/web/service/full/AnnotationService.java
+++ b/src/main/java/de/ids_mannheim/korap/web/service/full/AnnotationService.java
@@ -21,6 +21,7 @@
 import com.sun.jersey.spi.container.ResourceFilters;
 
 import de.ids_mannheim.korap.dao.AnnotationDao;
+import de.ids_mannheim.korap.dto.FoundryDto;
 import de.ids_mannheim.korap.dto.LayerDto;
 import de.ids_mannheim.korap.dto.converter.AnnotationConverter;
 import de.ids_mannheim.korap.entity.AnnotationPair;
@@ -62,16 +63,15 @@
     @Path("layers")
     public Response getLayers () {
         List<AnnotationPair> layers = annotationDao.getAllFoundryLayerPairs();
+        jlog.debug("/layers " + layers.toString());
         List<LayerDto> layerDto = annotationConverter.convertToLayerDto(layers);
         String result = JsonUtils.toJSON(layerDto);
-        jlog.debug("/layers " + layers.toString());
         return Response.ok(result).build();
     }
 
 
     /**
-     * Returns descriptions and values of the given foundry-layer
-     * pairs.
+     * Returns a list of foundry descriptions.
      * 
      * @param codes
      *            foundry-layer code or a Kleene-star
@@ -82,10 +82,12 @@
     @POST
     @Path("description")
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response getAnnotations (String json) {
+    public Response getFoundryDescriptions (String json) {
         JsonNode node = JsonUtils.readTree(json);
-        if (node == null) { throw KustvaktResponseHandler.throwit(
-                StatusCodes.MISSING_ARGUMENT, "Missing a json string."); }
+        if (node == null) {
+            throw KustvaktResponseHandler.throwit(StatusCodes.MISSING_ARGUMENT,
+                    "Missing a json string.");
+        }
 
         String language;
         if (!node.has("language")) {
@@ -96,6 +98,11 @@
             if (language == null || language.isEmpty()) {
                 language = "en";
             }
+            else if (!(language.equals("en") || language.equals("de"))) {
+                throw KustvaktResponseHandler.throwit(
+                        StatusCodes.UNSUPPORTED_VALUE, "Unsupported value:",
+                        language);
+            }
         }
 
         List<String> codes;
@@ -103,11 +110,13 @@
             codes = JsonUtils.convert(node.get("codes"), List.class);
         }
         catch (IOException | NullPointerException e) {
-            throw KustvaktResponseHandler.throwit(StatusCodes.PARAMETER_INVALID,
-                    "Bad parameter:", json);
+            throw KustvaktResponseHandler.throwit(StatusCodes.INVALID_ARGUMENT,
+                    "Bad argument:", json);
         }
-        if (codes == null) { throw KustvaktResponseHandler
-                .throwit(StatusCodes.MISSING_ARGUMENT); }
+        if (codes == null) {
+            throw KustvaktResponseHandler.throwit(StatusCodes.MISSING_ATTRIBUTE,
+                    "Missing attribute:", "codes");
+        }
 
         List<AnnotationPair> annotationPairs = null;
         String foundry = "", layer = "";
@@ -131,7 +140,7 @@
                 else {
                     jlog.error("Annotation code is wrong: " + annotationCode);
                     throw KustvaktResponseHandler.throwit(
-                            StatusCodes.PARAMETER_INVALID, "Bad parameter:",
+                            StatusCodes.INVALID_ATTRIBUTE, "Bad attribute:",
                             code);
                 }
 
@@ -141,8 +150,10 @@
         }
 
         if (annotationPairs != null && !annotationPairs.isEmpty()) {
-            String result = JsonUtils.toJSON(annotationPairs);
+            List<FoundryDto> dtos = annotationConverter
+                    .convertToFoundryDto(annotationPairs, language);
             jlog.debug("/layers " + annotationPairs.toString());
+            String result = JsonUtils.toJSON(dtos);
             return Response.ok(result).build();
         }
         else {
diff --git a/src/main/resources/db/new-mysql/V1__create_tables.sql b/src/main/resources/db/new-mysql/V1__create_tables.sql
index e68a45f..8217c27 100644
--- a/src/main/resources/db/new-mysql/V1__create_tables.sql
+++ b/src/main/resources/db/new-mysql/V1__create_tables.sql
@@ -4,6 +4,7 @@
 	code VARCHAR(20) NOT NULL,
 	type VARCHAR(20) NOT NULL,	
 	description VARCHAR(100) NOT NULL,
+	de_description VARCHAR(100),
 	UNIQUE INDEX unique_index (code, type)
 );
 
@@ -12,7 +13,6 @@
 	annotation1 INTEGER NOT NULL,
 	annotation2 INTEGER NOT NULL,
 	description VARCHAR(300) NOT NULL,
-	de_description VARCHAR(300),
 	UNIQUE INDEX unique_index (annotation1, annotation2),
 	FOREIGN KEY (annotation1)
 		REFERENCES annotation (id)