Index: /CQPWebservice/pom.xml
===================================================================
--- /CQPWebservice/pom.xml	(revision 2182)
+++ /CQPWebservice/pom.xml	(revision 2182)
@@ -0,0 +1,91 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>de.tuebingen.uni.sfs</groupId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>war</packaging>
+
+    <name>CQP Webservice</name>
+
+    <properties>
+        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>wlfxb</groupId>
+            <artifactId>wlfxb</artifactId>
+            <version>1_0</version>
+            <type>jar</type>
+        </dependency>
+        <dependency>
+            <groupId>eu.clarin.cqi</groupId>
+            <artifactId>cqi-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>1.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax</groupId>
+            <artifactId>javaee-web-api</artifactId>
+            <version>6.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                    <compilerArguments>
+                        <endorseddirs>${endorsed.dir}</endorseddirs>
+                    </compilerArguments>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>2.1.1</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>2.1</version>
+                <executions>
+                    <execution>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${endorsed.dir}</outputDirectory>
+                            <silent>true</silent>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>javax</groupId>
+                                    <artifactId>javaee-endorsed-api</artifactId>
+                                    <version>6.0</version>
+                                    <type>jar</type>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <artifactId>cqp-webservice</artifactId>
+</project>
Index: /CQPWebservice/src/main/java/eu/clarin/cqi/apps/CQPApplication.java
===================================================================
--- /CQPWebservice/src/main/java/eu/clarin/cqi/apps/CQPApplication.java	(revision 2182)
+++ /CQPWebservice/src/main/java/eu/clarin/cqi/apps/CQPApplication.java	(revision 2182)
@@ -0,0 +1,53 @@
+package eu.clarin.cqi.apps;
+
+import eu.clarin.cqi.ws.resources.CQPResource;
+import java.util.Collections;
+import java.util.Set;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ *
+ * @author akislev
+ */
+@ApplicationPath("cqp")
+public class CQPApplication extends Application {
+
+    @Override
+    public Set<Object> getSingletons() {
+        CQPResource resource = new CQPResource();
+        try {
+            InitialContext context = new InitialContext();
+            String cqiServerHostname = (String) context.lookup("java:comp/env/cqi-server-hostname");
+            if (cqiServerHostname == null || cqiServerHostname.isEmpty()) {
+                throw new RuntimeException("cqi-server-hostname <env-entry> is not specified in web.xml");
+            }
+            resource.setCqiServerHostname(cqiServerHostname);
+            Integer cqiServerPort = (Integer) context.lookup("java:comp/env/cqi-server-port");
+            if (cqiServerPort == null) {
+                throw new RuntimeException("cqi-server-port <env-entry> is not specified in web.xml");
+            }
+            resource.setCqiServerPort(cqiServerPort);
+            String cqiServerUsername = (String) context.lookup("java:comp/env/cqi-server-username");
+            if (cqiServerUsername == null || cqiServerUsername.isEmpty()) {
+                throw new RuntimeException("cqi-server-username <env-entry> is not specified in web.xml");
+            }
+            resource.setCqiServerUsername(cqiServerUsername);
+            String cqiServerPassword = (String) context.lookup("java:comp/env/cqi-server-password");
+            if (cqiServerPassword == null || cqiServerPassword.isEmpty()) {
+                throw new RuntimeException("cqi-server-password <env-entry> is not specified in web.xml");
+            }
+            resource.setCqiServerPassword(cqiServerPassword);
+            Integer maxQueryResults = (Integer) context.lookup("java:comp/env/max-query-results");
+            if (maxQueryResults == null) {
+                throw new RuntimeException("max-query-results <env-entry> is not specified in web.xml");
+            }
+            resource.setMaxQueryResults(maxQueryResults);
+        } catch (NamingException ex) {
+            throw new RuntimeException(ex.getExplanation(), ex);
+        }
+        return Collections.<Object>singleton(resource);
+    }
+}
Index: /CQPWebservice/src/main/java/eu/clarin/cqi/ws/impl/QueryEngine.java
===================================================================
--- /CQPWebservice/src/main/java/eu/clarin/cqi/ws/impl/QueryEngine.java	(revision 2182)
+++ /CQPWebservice/src/main/java/eu/clarin/cqi/ws/impl/QueryEngine.java	(revision 2182)
@@ -0,0 +1,165 @@
+package eu.clarin.cqi.ws.impl;
+
+import de.tuebingen.uni.sfs.wlf1.tc.api.*;
+import de.tuebingen.uni.sfs.wlf1.tc.xb.TextCorpusStored;
+import de.tuebingen.uni.sfs.wlf1.xb.WLData;
+import eu.clarin.cqi.client.CqiClient;
+import eu.clarin.cqi.client.CqiClientException;
+import eu.clarin.cqi.client.CqiResult;
+import java.util.*;
+
+/**
+ *
+ * @author akislev
+ */
+public class QueryEngine {
+
+    private static final String CONTEXT_STRUCTURAL_ATTRIBUTE = "s";
+    private static final String LANG = "de";
+    private static final String TAG_SET = "stts";
+    private final CqiClient client;
+
+    public QueryEngine(String serverHost, int serverPort, String username, String password) throws QueryEngineException {
+        try {
+            client = new CqiClient(serverHost, serverPort);
+        } catch (CqiClientException ex) {
+            throw new QueryEngineException("can't initialize a cqi client", ex);
+        }
+        try {
+            client.connect(username, password);
+        } catch (CqiClientException ex) {
+            throw new QueryEngineException("can't connect to the cqi server", ex);
+        }
+    }
+
+    public void close() throws QueryEngineException {
+        try {
+            client.disconnect();
+        } catch (CqiClientException ex) {
+            throw new QueryEngineException("can't disconnect from the cqi server", ex);
+        }
+    }
+
+    public WLData query(String corpus, String query, int startRecord, int maximumMatches) throws QueryEngineException, CqiClientException {
+        String[] mdStructAttr = getStructuralAttributeWithValues(corpus);
+        CqiResult result = client.query(corpus, query, CONTEXT_STRUCTURAL_ATTRIBUTE, mdStructAttr);
+        if (result.size() == 0) {
+            throw new QueryEngineException("No results found.");
+        }
+        if (maximumMatches < 1) {
+            result.clear();
+            throw new QueryEngineException("too few matches requested " + maximumMatches);
+        }
+        if (result.size() != 0) {
+            if (startRecord < 1 || startRecord > result.size()) {
+                throw new QueryEngineException("First record position out of range.");
+            }
+        }
+        String wordAttr = "word";
+        String lemmaAttr = "lemma";
+        String posAttr = "pos";
+        for (String attr : client.corpusPositionalAttributes(corpus)) {
+            if (attr.contains(wordAttr)) {
+                wordAttr = attr;
+            }
+            if (attr.contains(lemmaAttr)) {
+                lemmaAttr = attr;
+            }
+            if (attr.contains(posAttr)) {
+                posAttr = attr;
+            }
+        }
+        TextCorpusStored textCorpus = new TextCorpusStored(LANG);
+        TextLayer textLayer = textCorpus.createTextLayer();
+        TokensLayer tokensLayer = textCorpus.createTokensLayer();
+        SentencesLayer sentenceLayer = textCorpus.createSentencesLayer();
+        LemmasLayer lemmasLayer = textCorpus.createLemmasLayer();
+        PosTagsLayer posTagsLayer = textCorpus.createPosTagsLayer(TAG_SET);
+        MatchesLayer matchesLayer = textCorpus.createMatchesLayer("cqp", query);
+        StringBuilder sb = new StringBuilder();
+        boolean isFirst = true;
+        MatchedCorpus matchedCorpus = matchesLayer.addCorpus(corpus, null);
+        if (result.absolute(startRecord - 1)) {
+            do {
+                int matchStart = result.getMatchStart();
+                int matchEnd = result.getMatchEnd();
+                int contextStart = result.getContextStart();
+                int contextEnd = result.getContextEnd();
+                int target = result.getTarget();
+                int leftContextSize = matchStart - contextStart;
+                int targetOffset;
+                if (target != 0) {
+                    targetOffset = target - contextStart;
+                } else {
+                    targetOffset = -1;
+                }
+                int relMatchEnd = matchEnd - contextStart;
+                String[] words = result.getValues(wordAttr, contextStart, contextEnd);
+                String[] lemmas = result.getValues(lemmaAttr, contextStart, contextEnd);
+                String[] tags = result.getValues(posAttr, contextStart, contextEnd);
+                List<Token> sentence = new ArrayList<Token>(contextEnd - contextStart + 1);
+                List<Token> matchedTokens = new ArrayList<Token>(matchEnd - matchStart + 1);
+                Token targetToken = null;
+                for (int j = 0; j < words.length; j++) {
+                    String word = words[j];
+                    Token token = tokensLayer.addToken(word);
+                    sentence.add(token);
+                    if (isFirst) {
+                        isFirst = false;
+                    } else {
+                        sb.append(' ');
+                    }
+                    sb.append(word);
+                    lemmasLayer.addLemma(lemmas[j], token);
+                    posTagsLayer.addTag(tags[j], token);
+                    if (j >= leftContextSize && j <= relMatchEnd) {
+                        matchedTokens.add(token);
+                    }
+                    if (j == targetOffset) {
+                        targetToken = token;
+                    }
+                }
+                sentenceLayer.addSentence(sentence);
+                Map<String, String> targets = new HashMap<String, String>(1);
+                if (targetToken != null) {
+                    targets.put("0", targetToken.getID());
+                }
+                Map<String, String> metadata = new HashMap<String, String>(mdStructAttr.length);
+                for (int i = 0; i < mdStructAttr.length; i++) {
+                    String value = result.getStructuralAttributeValue(i);
+                    if (!value.isEmpty()) {
+                        metadata.put(mdStructAttr[i], value);
+                    }
+                }
+                matchesLayer.addItem(matchedCorpus, matchedTokens, createIntList(matchStart, matchEnd), targets, metadata);
+                if (--maximumMatches == 0) {
+                    break;
+                }
+            } while (result.next());
+            result.clear();
+            textLayer.addText(sb.toString());
+            return new WLData(textCorpus);
+        }
+        return null;
+    }
+
+    private static List<String> createIntList(int from, int to) {
+        String[] ints = new String[to - from + 1];
+        for (int i = 0; i < ints.length; i++) {
+            ints[i] = Integer.toString(from + i);
+        }
+        return Arrays.asList(ints);
+    }
+
+    private String[] getStructuralAttributeWithValues(String corpus) throws CqiClientException {
+        List<String> attrs = new ArrayList<String>();
+        String[] structuralAttributes = client.corpusStructuralAttributes(corpus);
+        for (String attr : structuralAttributes) {
+            String attrName = String.format("%s.%s", corpus, attr);
+            if (client.corpusStructuralAttributeHasValues(attrName)) {
+                attrs.add(attr);
+            }
+        }
+        return attrs.toArray(new String[attrs.size()]);
+    }
+}
Index: /CQPWebservice/src/main/java/eu/clarin/cqi/ws/impl/QueryEngineException.java
===================================================================
--- /CQPWebservice/src/main/java/eu/clarin/cqi/ws/impl/QueryEngineException.java	(revision 2182)
+++ /CQPWebservice/src/main/java/eu/clarin/cqi/ws/impl/QueryEngineException.java	(revision 2182)
@@ -0,0 +1,28 @@
+/**
+ * This software is copyright (c) 2012 by
+ *  - Seminar fuer Sprachwissenschaft  (http://www.sfs.uni-tuebingen.de/)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Seminar fuer Sprachwissenschaft (http://www.sfs.uni-tuebingen.de/)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.cqi.ws.impl;
+
+public class QueryEngineException extends Exception {
+
+    public QueryEngineException(String message) {
+        super(message);
+    }
+
+    public QueryEngineException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
Index: /CQPWebservice/src/main/java/eu/clarin/cqi/ws/resources/CQPResource.java
===================================================================
--- /CQPWebservice/src/main/java/eu/clarin/cqi/ws/resources/CQPResource.java	(revision 2182)
+++ /CQPWebservice/src/main/java/eu/clarin/cqi/ws/resources/CQPResource.java	(revision 2182)
@@ -0,0 +1,74 @@
+package eu.clarin.cqi.ws.resources;
+
+import eu.clarin.cqi.ws.impl.QueryEngine;
+import eu.clarin.cqi.ws.impl.QueryEngineException;
+import de.tuebingen.uni.sfs.wlf1.xb.WLData;
+import eu.clarin.cqi.client.CqiClientException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+
+/**
+ *
+ * @author akislev
+ */
+@Path("")
+public class CQPResource {
+
+    private String cqiServerHostname;
+    private int cqiServerPort;
+    private String cqiServerUsername;
+    private String cqiServerPassword;
+    private int maxQueryResults;
+
+    public void setCqiServerHostname(String cqiServerHostname) {
+        this.cqiServerHostname = cqiServerHostname;
+    }
+
+    public void setCqiServerPassword(String cqiServerPassword) {
+        this.cqiServerPassword = cqiServerPassword;
+    }
+
+    public void setCqiServerPort(int cqiServerPort) {
+        this.cqiServerPort = cqiServerPort;
+    }
+
+    public void setCqiServerUsername(String cqiServerUsername) {
+        this.cqiServerUsername = cqiServerUsername;
+    }
+
+    public void setMaxQueryResults(int maxQueryResults) {
+        this.maxQueryResults = maxQueryResults;
+    }
+
+    @GET
+    @Path("query")
+    @Produces(MediaType.TEXT_XML)
+    public WLData getQueryCQP(@QueryParam("query") String query, @DefaultValue("tueba-ddc") @QueryParam("corpus") String corpusName, @DefaultValue("1") @QueryParam("start") int startRecord, @DefaultValue("10") @QueryParam("results") int resultsNumber) {
+        if (query == null) {
+            throw new ResourceException("\"query\" parameter is not specified");
+        }
+        return postQueryCQP(query, corpusName, startRecord, resultsNumber);
+    }
+
+    @POST
+    @Path("query")
+    @Consumes("text/query-cqp")
+    @Produces("text/tcf+xml")
+    public WLData postQueryCQP(String query, @DefaultValue("tueba-ddc") @QueryParam("corpus") final String corpusName, @DefaultValue("1") @QueryParam("start") int startRecord, @DefaultValue("10") @QueryParam("results") int resultsNumber) {
+        if (resultsNumber > maxQueryResults || resultsNumber < 1) {
+            throw new ResourceException("bad results parameter value");
+        }
+        try {
+            QueryEngine queryEngine = new QueryEngine(cqiServerHostname, cqiServerPort, cqiServerUsername, cqiServerPassword);
+            WLData data = queryEngine.query(corpusName.toUpperCase(), query, startRecord, resultsNumber);
+            queryEngine.close();
+            return data;
+        } catch (QueryEngineException ex) {
+            throw new ResourceException(ex);
+        } catch (CqiClientException ex) {
+            throw new ResourceException(ex);
+        }
+    }
+}
Index: /CQPWebservice/src/main/java/eu/clarin/cqi/ws/resources/ResourceException.java
===================================================================
--- /CQPWebservice/src/main/java/eu/clarin/cqi/ws/resources/ResourceException.java	(revision 2182)
+++ /CQPWebservice/src/main/java/eu/clarin/cqi/ws/resources/ResourceException.java	(revision 2182)
@@ -0,0 +1,20 @@
+package eu.clarin.cqi.ws.resources;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ *
+ * @author akislev
+ */
+public class ResourceException extends WebApplicationException {
+
+    public ResourceException(String message) {
+        super(Response.status(Response.Status.BAD_REQUEST).entity(message).type(MediaType.TEXT_PLAIN_TYPE).build());
+    }
+
+    public ResourceException(Throwable cause) {
+        super(Response.status(Response.Status.BAD_REQUEST).entity(cause.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build());
+    }
+}
Index: /CQPWebservice/src/main/webapp/META-INF/context.xml
===================================================================
--- /CQPWebservice/src/main/webapp/META-INF/context.xml	(revision 2182)
+++ /CQPWebservice/src/main/webapp/META-INF/context.xml	(revision 2182)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Context antiJARLocking="true" path="/cqp-ws"/>
Index: /CQPWebservice/src/main/webapp/WEB-INF/web.xml
===================================================================
--- /CQPWebservice/src/main/webapp/WEB-INF/web.xml	(revision 2182)
+++ /CQPWebservice/src/main/webapp/WEB-INF/web.xml	(revision 2182)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+    <env-entry>
+        <env-entry-name>cqi-server-hostname</env-entry-name>
+        <env-entry-type>java.lang.String</env-entry-type>
+        <env-entry-value></env-entry-value>
+    </env-entry>
+    <env-entry>
+        <env-entry-name>cqi-server-port</env-entry-name>
+        <env-entry-type>java.lang.Integer</env-entry-type>
+        <env-entry-value>4877</env-entry-value>
+    </env-entry>
+    <env-entry>
+        <env-entry-name>cqi-server-username</env-entry-name>
+        <env-entry-type>java.lang.String</env-entry-type>
+        <env-entry-value></env-entry-value>
+    </env-entry>
+    <env-entry>
+        <env-entry-name>cqi-server-password</env-entry-name>
+        <env-entry-type>java.lang.String</env-entry-type>
+        <env-entry-value></env-entry-value>
+    </env-entry>
+    <env-entry>
+        <env-entry-name>max-query-results</env-entry-name>
+        <env-entry-type>java.lang.Integer</env-entry-type>
+        <env-entry-value>100</env-entry-value>
+    </env-entry>
+</web-app>
Index: /CQPWebservice/src/main/webapp/index.jsp
===================================================================
--- /CQPWebservice/src/main/webapp/index.jsp	(revision 2182)
+++ /CQPWebservice/src/main/webapp/index.jsp	(revision 2182)
@@ -0,0 +1,23 @@
+<%@page contentType="text/html" pageEncoding="UTF-8"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+    "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <title>CQP Webservice Test Page</title>
+    </head>
+    <body>
+        <form action="cqp/query" method="get">
+            <label for="corpus">Corpus: </label>
+            <input type="text" name="corpus" value="tueba-ddc"/>
+            <label for="lastname">Query: </label>
+            <input type="text" name="query" value="'Begriff'"/>
+            <label for="start">First result offset: </label>
+            <input type="text" name="start" value="1"/>
+            <label for="start">Number of results: </label>
+            <input type="text" name="results" value="10"/>
+            <input type="submit" value="Query"/>
+        </form>
+    </body>
+</html>
