Added virtual corpus service (store).

Change-Id: Ic8073d801bc324b4f51fd0a0222c9e48a1ad17f9
diff --git a/full/src/main/java/de/ids_mannheim/korap/dto/VirtualCorpusDto.java b/full/src/main/java/de/ids_mannheim/korap/dto/VirtualCorpusDto.java
new file mode 100644
index 0000000..6f44a4c
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/dto/VirtualCorpusDto.java
@@ -0,0 +1,7 @@
+package de.ids_mannheim.korap.dto;
+
+public class VirtualCorpusDto {
+
+    int id;
+    
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java b/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
new file mode 100644
index 0000000..f866aeb
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
@@ -0,0 +1,103 @@
+package de.ids_mannheim.korap.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.constant.VirtualCorpusType;
+import de.ids_mannheim.korap.dao.VirtualCorpusDao;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.User.CorpusAccess;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KoralCollectionQueryBuilder;
+import de.ids_mannheim.korap.web.SearchKrill;
+import de.ids_mannheim.korap.web.input.VirtualCorpusFromJson;
+
+@Service
+public class VirtualCorpusService {
+
+    private static Logger jlog =
+            LoggerFactory.getLogger(VirtualCorpusService.class);
+
+    @Autowired
+    private VirtualCorpusDao dao;
+
+    @Autowired
+    private SearchKrill krill;
+
+    @Autowired
+    private KustvaktConfiguration config;
+
+    public void storeVC (VirtualCorpusFromJson vc, User user)
+            throws KustvaktException {
+
+        // EM: how about VirtualCorpusType.PUBLISHED?
+        if (vc.getType().equals(VirtualCorpusType.PREDEFINED)
+                && !user.isAdmin()) {
+            throw new KustvaktException(StatusCodes.UNAUTHORIZED_OPERATION,
+                    "Unauthorized operation for user: " + user.getUsername(),
+                    user.getUsername());
+        }
+
+        String koralQuery = serializeCollectionQuery(vc.getCollectionQuery());
+        CorpusAccess requiredAccess = determineRequiredAccess(koralQuery);
+
+        dao.createVirtualCorpus(vc.getName(), vc.getType(), requiredAccess,
+                koralQuery, vc.getDefinition(), vc.getDescription(),
+                vc.getStatus(), vc.getCreatedBy());
+
+        // EM: should this return anything?
+    }
+
+    private String serializeCollectionQuery (String collectionQuery)
+            throws KustvaktException {
+        QuerySerializer serializer = new QuerySerializer();
+        serializer.setCollection(collectionQuery);
+        String koralQuery;
+        try {
+            koralQuery = serializer.convertCollectionToJson();
+        }
+        catch (JsonProcessingException e) {
+            throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
+                    "Invalid argument: " + collectionQuery, collectionQuery);
+        }
+        jlog.debug(koralQuery);
+        return koralQuery;
+    }
+
+    private CorpusAccess determineRequiredAccess (String koralQuery)
+            throws KustvaktException {
+
+        if (findDocWithLicense(koralQuery, config.getAllOnlyRegex())) {
+            return CorpusAccess.ALL;
+        }
+        else if (findDocWithLicense(koralQuery, config.getPublicOnlyRegex())) {
+            return CorpusAccess.PUB;
+        }
+        else {
+            return CorpusAccess.FREE;
+        }
+    }
+
+    private boolean findDocWithLicense (String koralQuery, String license)
+            throws KustvaktException {
+        KoralCollectionQueryBuilder koral = new KoralCollectionQueryBuilder();
+        koral.setBaseQuery(koralQuery);
+        koral.with("availability=/" + license + "/");
+        String json = koral.toJSON();
+
+        String statistics = krill.getStatistics(json);
+        JsonNode node = JsonUtils.readTree(statistics);
+        int numberOfDoc = node.at("/documents").asInt();
+        jlog.debug("License: " + license + ", number of docs: " + numberOfDoc);
+        return (numberOfDoc > 0) ? true : false;
+    }
+}
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
new file mode 100644
index 0000000..aafaa10
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
@@ -0,0 +1,79 @@
+package de.ids_mannheim.korap.web.controller;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+
+import com.sun.jersey.spi.container.ResourceFilters;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.filter.AuthFilter;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.service.VirtualCorpusService;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.ParameterChecker;
+import de.ids_mannheim.korap.web.filter.DemoUserFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import de.ids_mannheim.korap.web.input.VirtualCorpusFromJson;
+import de.ids_mannheim.korap.web.utils.KustvaktResponseHandler;
+
+@Controller
+@Path("vc")
+@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+@ResourceFilters({ AuthFilter.class, DemoUserFilter.class, PiwikFilter.class })
+public class VirtualCorpusController {
+
+    private static Logger jlog =
+            LoggerFactory.getLogger(VirtualCorpusController.class);
+
+    @Autowired
+    private AuthenticationManagerIface authManager;
+    @Autowired
+    private KustvaktResponseHandler responseHandler;
+    @Autowired
+    private VirtualCorpusService service;
+
+    @POST
+    @Path("store")
+    public Response storeVC (@Context SecurityContext securityContext,
+            String json) {
+        try {
+            ParameterChecker.checkStringValue(json, "json string");
+
+            // create vc object from json
+            VirtualCorpusFromJson vc =
+                    JsonUtils.convertToClass(json, VirtualCorpusFromJson.class);
+            jlog.debug(vc.toString());
+
+            // get user info
+            TokenContext context =
+                    (TokenContext) securityContext.getUserPrincipal();
+            if (context.isDemo()) {
+                throw new KustvaktException(StatusCodes.UNAUTHORIZED_OPERATION,
+                        "Operation is not permitted for user: "
+                                + context.getUsername(),
+                        context.getUsername());
+            }
+
+            User user = authManager.getUser(context.getUsername());
+            service.storeVC(vc, user);
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+        return Response.ok().build();
+    }
+
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/VirtualCorpusFromJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/VirtualCorpusFromJson.java
new file mode 100644
index 0000000..d6ddbd0
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/VirtualCorpusFromJson.java
@@ -0,0 +1,38 @@
+package de.ids_mannheim.korap.web.input;
+
+import de.ids_mannheim.korap.constant.VirtualCorpusType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.ParameterChecker;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VirtualCorpusFromJson {
+
+    private String name;
+    private VirtualCorpusType type;
+    private String createdBy;
+    private String collectionQuery;
+
+    // optional
+    private String definition;
+    private String description;
+    private String status;
+
+    public void setName (String name) throws KustvaktException {
+        ParameterChecker.checkStringValue(name, "name");
+        this.name = name;
+    }
+
+    public void setCreatedBy (String createdBy) throws KustvaktException {
+        ParameterChecker.checkStringValue(createdBy, "createdBy");
+        this.createdBy = createdBy;
+    }
+
+    public void setCollectionQuery (String collectionQuery)
+            throws KustvaktException {
+        ParameterChecker.checkStringValue(collectionQuery, "collectionQuery");
+        this.collectionQuery = collectionQuery;
+    }
+}
\ No newline at end of file
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/service/full/VirtualCorpusServiceTest.java b/full/src/test/java/de/ids_mannheim/korap/web/service/full/VirtualCorpusServiceTest.java
new file mode 100644
index 0000000..7996ac6
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/service/full/VirtualCorpusServiceTest.java
@@ -0,0 +1,105 @@
+package de.ids_mannheim.korap.web.service.full;
+
+import static org.junit.Assert.*;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.ContextLoaderListener;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
+import com.sun.jersey.test.framework.AppDescriptor;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+import com.sun.jersey.test.framework.spi.container.TestContainerException;
+import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
+import com.sun.jersey.test.framework.spi.container.grizzly.web.GrizzlyWebTestContainerFactory;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.security.auth.BasicHttpAuth;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class VirtualCorpusServiceTest extends JerseyTest {
+
+    private static String[] classPackages =
+            new String[] { "de.ids_mannheim.korap.web.service.full",
+                    "de.ids_mannheim.korap.web.filter",
+                    "de.ids_mannheim.korap.web.utils" };
+
+
+    @Override
+    protected TestContainerFactory getTestContainerFactory ()
+            throws TestContainerException {
+        return new GrizzlyWebTestContainerFactory();
+    }
+
+    @Override
+    protected AppDescriptor configure () {
+        return new WebAppDescriptor.Builder(classPackages)
+                .servletClass(SpringServlet.class)
+                .contextListenerClass(ContextLoaderListener.class)
+                .contextParam("contextConfigLocation",
+                        "classpath:test-config.xml")
+                .build();
+    }
+
+    @Test
+    public void testStoreVC () throws KustvaktException {
+        String json =
+                "{\"name\": \"new vc\",\"type\": \"PRIVATE\",\"createdBy\": "
+                        + "\"test class\",\"collectionQuery\": \"corpusSigle=GOE\"}";
+
+        ClientResponse response = resource().path("vc").path("store")
+                .header(Attributes.AUTHORIZATION,
+                        BasicHttpAuth.encode("kustvakt", "kustvakt2015"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .entity(json)
+                .post(ClientResponse.class);
+        String entity = response.getEntity(String.class);
+        System.out.println(entity);
+    }
+
+    @Test
+    public void testStoreVCUnauthorized () throws KustvaktException {
+        String json =
+                "{\"name\": \"new vc\",\"type\": \"PRIVATE\",\"createdBy\": "
+                        + "\"test class\",\"collectionQuery\": \"pubDate eq 1982\"}";
+
+        ClientResponse response = resource().path("vc").path("store")
+                .entity(json).post(ClientResponse.class);
+        String entity = response.getEntity(String.class);
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.UNAUTHORIZED_OPERATION,
+                node.at("/errors/0/0").asInt());
+    }
+
+    @Test
+    public void testStoreVCWithWrongType () throws KustvaktException {
+        String json =
+                "{\"name\": \"new vc\",\"type\": \"PRIVAT\",\"createdBy\": "
+                        + "\"test class\",\"collectionQuery\": \"pubDate eq 1982\"}";
+
+        ClientResponse response = resource().path("vc").path("store")
+                .entity(json).post(ClientResponse.class);
+        String entity = response.getEntity(String.class);
+        //        System.out.println(entity);
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.DESERIALIZATION_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertTrue(node.at("/errors/0/1").asText().startsWith(
+                "Cannot deserialize value of type `de.ids_mannheim.korap.constant."
+                        + "VirtualCorpusType` from String \"PRIVAT\": value not one of "
+                        + "declared Enum instance names: [PROJECT, PRIVATE, PREDEFINED, "
+                        + "PUBLISHED]"));
+    }
+}