Bugfix: Queries containing curly brackets  (closes #61)

Change-Id: Ic3a536e59c9e8ae1793b43045b36d43a0e70b710
diff --git a/Changes b/Changes
index 54f0b4e..4895ff9 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,7 @@
+0.3.2 2025-9-29
+    - Bugfix: export of queries containing curly brackets (closes #61 )(hebasta)
+    - Upgrade dependencies following dependabot
+
 0.3.1 2024-10-9
     - Produces export template as service (hebasta)
     - Produces export template on the command line  (hebasta)
diff --git a/pom.xml b/pom.xml
index eb4aa05..8b4cbe3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   <groupId>de.ids_mannheim.korap</groupId>
   <artifactId>KalamarExportPlugin</artifactId>
   <packaging>jar</packaging>
-  <version>0.3.1</version>
+  <version>0.3.2</version>
   <name>KalamarExportPlugin</name>
   <url>https://korap.ids-mannheim.de/</url>
   
diff --git a/src/main/java/de/ids_mannheim/korap/plkexport/Service.java b/src/main/java/de/ids_mannheim/korap/plkexport/Service.java
index 6b9d02f..d6c6cf3 100644
--- a/src/main/java/de/ids_mannheim/korap/plkexport/Service.java
+++ b/src/main/java/de/ids_mannheim/korap/plkexport/Service.java
@@ -185,12 +185,13 @@
         ResponseBuilder builder = null;
         Client client = ClientBuilder.newClient();
         
+        String query = q.replace("{", "%7B").replace("}", "%7D");
         // Create initial search uri
         UriBuilder uri = UriBuilder.fromPath("/api/v1.0/search")
             .host(host)
             .port(Integer.parseInt(port))
             .scheme(scheme)
-            .queryParam("q", q)
+            .queryParam("q", query)
             .queryParam("context", "40-t,40-t")
             .queryParam("ql", ql)
             .queryParam("count", pageSize)
diff --git a/src/test/java/de/ids_mannheim/korap/plkexport/ServiceTest.java b/src/test/java/de/ids_mannheim/korap/plkexport/ServiceTest.java
index 1c3d07f..d56f703 100644
--- a/src/test/java/de/ids_mannheim/korap/plkexport/ServiceTest.java
+++ b/src/test/java/de/ids_mannheim/korap/plkexport/ServiceTest.java
@@ -192,7 +192,37 @@
                 Status.OK.getStatusCode(), responsejson.getStatus());
     };
     
-    
+    @Test
+    public void testExportWsQueryWithCurlyBrackets () {
+        mockClient.reset().when(
+            request()
+            .withMethod("GET")
+            .withPath("/api/v1.0/search")
+            .withQueryStringParameter("q", "der{3}")
+            )
+            .respond(
+                response()
+                .withStatusCode(200)
+                .withHeaders(new Header("Content-Type", "application/json; charset=UTF-8"))
+                .withBody(getFixture("response_der.json"))
+                );
+
+        String filenamej = "dateiJson";
+        MultivaluedHashMap<String, String> frmap = new MultivaluedHashMap<String, String>();
+        frmap.add("fname", filenamej);
+        frmap.add("format", "json");
+        frmap.add("q", "der{3}");
+        frmap.add("ql", "poliqarp");
+        frmap.add("cutoff", "true");
+
+
+        Response responsejson = target("/export").request()
+                .post(Entity.form(frmap));
+     
+        assertEquals("Request JSON: Http Response should be 200: ",
+                Status.OK.getStatusCode(), responsejson.getStatus());
+    };
+
     @Test
     public void testExportWsRTF () {
         mockClient.reset().when(
diff --git a/src/test/resources/fixtures/response_der.json b/src/test/resources/fixtures/response_der.json
new file mode 100644
index 0000000..96fe54d
--- /dev/null
+++ b/src/test/resources/fixtures/response_der.json
@@ -0,0 +1 @@
+{"query":{"operands":[{"@type":"koral:token","wrap":{"@type":"koral:term","match":"match:eq","layer":"orth","key":"der","foundry":"opennlp","rewrites":[{"@type":"koral:rewrite","src":"Kustvakt","editor":"Kustvakt","operation":"operation:injection","scope":"foundry","_comment":"Default foundry has been added."}]}}],"boundary":{"min":3,"max":3,"@type":"koral:boundary"},"@type":"koral:group","operation":"operation:repetition"},"meta":{"count":1,"startIndex":0,"timeout":10000,"context":{"left":["token",40],"right":["token",40]},"fields":["ID","UID","textSigle","corpusID","author","title","subTitle","textClass","pubPlace","pubDate","availability","layerInfos","docSigle","corpusSigle"],"version":"0.64.1","benchmark":"0.394243635 s","totalResources":-1,"totalResults":90,"serialQuery":"spanRepetition(tokens:s:der{3,3})","itemsPerPage":1},"collection":{"@type":"koral:docGroup","operation":"operation:and","operands":[{"@type":"koral:doc","match":"match:eq","type":"type:regex","value":"CC.*","key":"availability"},{"@type":"koral:doc","match":"match:eq","type":"type:regex","value":"CC-BY.*","key":"availability"}],"rewrites":[{"@type":"koral:rewrite","src":"Kustvakt","editor":"Kustvakt","operation":"operation:override","original":{"@type":"koral:doc","match":"match:eq","type":"type:regex","value":"CC-BY.*","key":"availability"},"_comment":"Free corpus access policy has been added."}]},"matches":[{"@context":"http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld","meta":{},"hasSnippet":true,"hasTokens":false,"matchID":"match-WUD17/E86/39827-p52302-52305","context":{"left":["token",40],"right":["token",40]},"snippet":"<span class=\"context-left\"><span class=\"more\"></span>nicht melden. Warum nicht mit richtiger Verlinkung signieren, sonst muss man die Versionsgeschichte durchwühlen (nicht besonders sinnvoll)? Also dann so:  . Das mit den Kollegen, die PAs sind, na ja, seltsame Sache, da scheint sich was aufgeschaukelt zu haben. Ist ER </span><span class=\"match\"><mark>der, der, der</mark></span><span class=\"context-right\"> oder sonst einer? -- 04:17, 9. Aug. 2015 (CEST) PS: Das Artikelchen sieht nun schon besser aus, Silvicola hat auch noch geglättet. Allerdings blieben doch viele der Fehler fast 2 Monate lang drin stehen (trotz der vielen „PA entfernt“, die so<span class=\"more\"></span></span>","fields":[{"@type":"koral:field","key":"ID"},{"@type":"koral:field","key":"textSigle","type":"type:string","value":"WUD17/E86/39827"},{"@type":"koral:field","key":"corpusID"},{"@type":"koral:field","key":"author","type":"type:text","value":"Elop, u.a."},{"@type":"koral:field","key":"title","type":"type:text","value":"Benutzer Diskussion:Elop/Archiv 2015"},{"@type":"koral:field","key":"subTitle"},{"@type":"koral:field","key":"textClass","type":"type:keywords","value":["staat-gesellschaft","biographien-interviews"]},{"@type":"koral:field","key":"pubPlace","type":"type:string","value":"URL:http://de.wikipedia.org"},{"@type":"koral:field","key":"pubDate","type":"type:date","value":"2017-07-01"},{"@type":"koral:field","key":"availability","type":"type:string","value":"CC-BY-SA"},{"@type":"koral:field","key":"layerInfos","type":"type:store","value":"corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens"},{"@type":"koral:field","key":"docSigle","type":"type:string","value":"WUD17/E86"},{"@type":"koral:field","key":"corpusSigle","type":"type:string","value":"WUD17"}],"textSigle":"WUD17/E86/39827","author":"Elop, u.a.","title":"Benutzer Diskussion:Elop/Archiv 2015","textClass":"staat-gesellschaft biographien-interviews","pubPlace":"URL:http://de.wikipedia.org","pubDate":"2017-07-01","availability":"CC-BY-SA","layerInfos":"corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens","docSigle":"WUD17/E86","corpusSigle":"WUD17"}]}
\ No newline at end of file