first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7ac1ad8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,65 @@
+# Eclipse project files
+.classpath
+.project
+.settings/
+.checkstyle
+
+# IntelliJ project files
+*.iml
+*.ipr
+*.iws
+
+# Additional tools
+.clover/
+
+# OSX files
+.DS_Store
+
+# Compiler output, class files
+*.class
+bin/
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#Test config and output
+/target/
+test-output/
+local/
+reports/
+.pmd
+tmp/
+
+# Build artifacts
+out/
+build/
+jarsIn/
+*.jar
+build.xml
+.idea
+*.properties
+*.releaseBackup
+*.next
+*.tag
+
+# Jekyll
+_site/
+_*
+_*/
diff --git a/DISCLAIMER-WIP b/DISCLAIMER-WIP
new file mode 100644
index 0000000..ae9f942
--- /dev/null
+++ b/DISCLAIMER-WIP
@@ -0,0 +1,26 @@
+Apache DataSketches (incubating) is an effort undergoing incubation
+at The Apache Software Foundation (ASF), sponsored by the Apache Incubator.
+
+Incubation is required of all newly accepted projects until a further review
+indicates that the infrastructure, communications, and decision making process
+have stabilized in a manner consistent with other successful ASF projects.
+
+While incubation status is not necessarily a reflection of the
+completeness or stability of the code, it does indicate that the
+project has yet to be fully endorsed by the ASF.
+
+Some of the incubating project's releases may not be fully compliant
+with ASF policy. For example, releases may have incomplete or
+un-reviewed licensing conditions. What follows is a list of known
+issues the project is currently aware of (note that this list, by
+definition, is likely to be incomplete):
+
+ * No issues are known at this time.
+
+If you are planning to incorporate this work into your
+product or project, please be aware that you will need to conduct a
+thorough licensing review to determine the overall implications of
+including this work. For the current status of this project through the Apache
+Incubator visit:
+
+http://incubator.apache.org/projects/datasketches.html
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f927b76
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,210 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+
+
+APPENDIX A: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+ -------------------------------------------------------------
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -------------------------------------------------------------
+
+
+
+APPENDIX B: Additional licenses relevant to this product:
+ (none)
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c8912d6
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,5 @@
+Apache DataSketches Server (incubating)
+Copyright 2020 - The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3f44901
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# DataSketches Server
+This is a very simple, container-friendly web server with a JSON API. The provided
+server can be used to experiment with sketches or to add sketch functionality to a
+project without natively integrating with the DataSketches library.
+
+This is not intended to provide a high-performance database; there are already
+existing integrations with PostgreSQL or Druid for such cases. The focus here
+is on ease-of-use rather than speed.
+
+The repository is still in a very early state and lacks both unit tests and
+robust documentation.
+
+----
+
+Disclaimer: Apache DataSketches is an effort undergoing incubation at The Apache
+Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required
+of all newly accepted projects until a further review indicates that the infrastructure,
+communications, and decision making process have stabilized in a manner consistent
+with other successful ASF projects. While incubation status is not necessarily a
+reflection of the completeness or stability of the code, it does indicate that the
+project has yet to be fully endorsed by the ASF.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4a9d6d4
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.datasketches</groupId>
+ <artifactId>datasketches-server</artifactId>
+ <packaging>jar</packaging>
+ <version>0.1.0</version>
+ <name>datasketches-server</name>
+ <url>https://datasketches.apache.org</url>
+ <inceptionYear>2020</inceptionYear>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.8.6</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <!-- DataSketches dependencies -->
+ <dependency>
+ <groupId>org.apache.datasketches</groupId>
+ <artifactId>datasketches-memory</artifactId>
+ <version>1.2.0-incubating</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.datasketches</groupId>
+ <artifactId>datasketches-java</artifactId>
+ <version>1.3.0-incubating</version>
+ </dependency>
+
+ <!--Jetty dependencies-->
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>9.4.31.v20200723</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <version>9.4.31.v20200723</version>
+ </dependency>
+
+ <!--Testing-->
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.14.3</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>${project.artifactId}-${project.version}</finalName>
+ <directory>target</directory>
+
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>org.apache.datasketches.server.SketchServer</mainClass>
+ </manifest>
+ </archive>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!--
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>2.0</version>
+ <configuration>
+ <findbugsXmlOutput>true</findbugsXmlOutput>
+ <xmlOutput>true</xmlOutput>
+ <threshold>High</threshold>
+ </configuration>
+ </plugin>
+ -->
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+ <!--
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
+ -->
+ </plugins>
+ </reporting>
+
+ <repositories>
+ <repository>
+ <id>apache.snapshots</id>
+ <name>Apache Snapshot Repository</name>
+ <url>https://repository.apache.org/content/groups/snapshots/org/apache/datasketches/</url>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>apache</id>
+ <name>Apache Releases Repository</name>
+ <url>https://repository.apache.org/content/repositories/releases/org/apache/datasketches/</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+</project>
diff --git a/src/main/java/org/apache/datasketches/server/BaseSketchesQueryHandler.java b/src/main/java/org/apache/datasketches/server/BaseSketchesQueryHandler.java
new file mode 100644
index 0000000..bad4af0
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/BaseSketchesQueryHandler.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+
+import org.apache.datasketches.Family;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import static org.apache.datasketches.server.SketchConstants.*;
+
+/**
+ * Provides a common request handler for the sketches server. This gives us several benefits:
+ * <ul>
+ * <li>Extracts JSON query from querystring or POST body, as appropriate, to allow multiple input types.</li>
+ * <li>Sketches are stateful, and even reading can be disrupted by writes on another thread. A real
+ * database would have a more sophisticated locking system, but this class lets us syncrhonize across
+ * query types.</li>
+ * <li>Handles both JSON arrays or single JSON objects as inputs, letting the query handlers avoid
+ * code duplication.
+ * </ul>
+ * By using this class, the individual query handlers are able to consume and emit only JSON objects; they
+ * need not worry about details of the HTTP request or response.
+ */
+public abstract class BaseSketchesQueryHandler extends AbstractHandler {
+ final SketchStorage sketches;
+ final boolean queryExempt;
+
+ /**
+ * Basic query handler. Assumes calls must include a JSON query.
+ * @param sketches The sketches database to use
+ */
+ BaseSketchesQueryHandler(SketchStorage sketches) {
+ this(sketches, false);
+ }
+
+ /**
+ * Basic query handler, allowing the derived type to explicitly declare if an input query is optional.
+ * @param sketches The sketches database to use
+ * @param queryExempt <tt>true</tt> if a query is not required, otherwise <tt>false</tt>
+ */
+ BaseSketchesQueryHandler(SketchStorage sketches, boolean queryExempt) {
+ if (sketches == null) {
+ throw new IllegalArgumentException("Cannot initialize handler with SketchStorage == null");
+ }
+ this.sketches = sketches;
+ this.queryExempt = queryExempt;
+ }
+
+ JsonElement checkMethodAndReadJson(Request baseRequest,
+ HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ JsonElement query = null;
+ if (request.getMethod().equals("POST")) {
+ response.setContentType("application/json");
+ query = JsonParser.parseReader(request.getReader());
+ } else if (request.getMethod().equals("GET")) {
+ response.setContentType("text/html");
+ query = JsonParser.parseString(URLDecoder.decode(request.getQueryString(), "utf-8"));
+ } else {
+ response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ baseRequest.setHandled(true);
+ }
+
+ return query;
+ }
+
+ /**
+ * Query handler to be implemented by subclasses
+ * @param query A JSON query to process
+ * @return A JSON response
+ */
+ protected abstract JsonObject processQuery(JsonObject query);
+
+ /**
+ * Internal method to synchronize calls to subclasses
+ * @param query A JSON query to process
+ * @return A JSON response
+ */
+ final synchronized JsonObject callProcessQuery(JsonObject query) {
+ return processQuery(query);
+ }
+
+ @Override
+ public void handle(String target,
+ Request baseRequest,
+ HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ JsonElement query = null;
+ if (!queryExempt && ((query = checkMethodAndReadJson(baseRequest, request, response)) == null)) {
+ return;
+ }
+
+ // error messages will be wrapped in json
+ response.setCharacterEncoding("utf-8");
+ response.setContentType("application/json");
+
+ JsonElement result = null;
+
+ try {
+ if (query == null) {
+ result = callProcessQuery(null);
+ } else if (query.isJsonArray()) {
+ for (JsonElement subQuery : query.getAsJsonArray()) {
+ JsonObject subResult = callProcessQuery(subQuery.getAsJsonObject());
+ if (subResult != null) {
+ // lazy initialization to avoid possibly empty array
+ if (result == null) {
+ result = new JsonArray(((JsonArray) query).size());
+ }
+ ((JsonArray) result).add(subResult);
+ }
+ }
+ } else {
+ result = callProcessQuery((JsonObject) query);
+ }
+
+ if (result != null) {
+ response.getWriter().print(result.toString());
+ //response.getWriter().print(new GsonBuilder().setPrettyPrinting().create().toJson(result));
+ }
+
+ // we're ok if we reach here without an exception
+ response.setStatus(HttpServletResponse.SC_OK);
+ } catch (Exception e) {
+ JsonObject error = new JsonObject();
+ error.addProperty(ERROR_KEY, e.getMessage());
+ response.setStatus(UNPROCESSABLE_ENTITY);
+ }
+
+ baseRequest.setHandled(true);
+ }
+
+ static Family familyFromString(String type) throws IllegalArgumentException {
+ switch (type.toLowerCase()) {
+ case SKETCH_FAMILY_THETA:
+ return Family.QUICKSELECT;
+
+ case SKETCH_FAMILY_KLL:
+ return Family.KLL;
+
+ case SKETCH_FAMILY_FREQUENCY:
+ return Family.FREQUENCY;
+
+ case SKETCH_FAMILY_HLL:
+ return Family.HLL;
+
+ case SKETCH_FAMILY_CPC:
+ return Family.CPC;
+
+ case SKETCH_FAMILY_RESERVOIR:
+ return Family.RESERVOIR;
+
+ case SKETCH_FAMILY_VAROPT:
+ return Family.VAROPT;
+
+ default:
+ throw new IllegalArgumentException("Unrecognized sketch type: " + type);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/DataQueryHandler.java b/src/main/java/org/apache/datasketches/server/DataQueryHandler.java
new file mode 100644
index 0000000..68255e4
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/DataQueryHandler.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import org.apache.datasketches.Family;
+import org.apache.datasketches.SketchesException;
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.frequencies.ErrorType;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsSamples;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.theta.CompactSketch;
+import org.apache.datasketches.theta.Union;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import static org.apache.datasketches.server.SketchConstants.*;
+
+/**
+ * Allows querying the sketches to obtain estimates of the relevant quantities.
+ *
+ * Full description to be added later.
+ */
+public class DataQueryHandler extends BaseSketchesQueryHandler {
+ public DataQueryHandler(SketchStorage sketches) {
+ super(sketches, false);
+ }
+
+ @Override
+ protected JsonObject processQuery(JsonObject query) {
+ if (!query.has(QUERY_NAME_FIELD)) {
+ throw new IllegalArgumentException("Query missing sketch name field");
+ }
+
+ String key = query.get(QUERY_NAME_FIELD).getAsString();
+ if (!sketches.contains(key)) {
+ throw new IllegalArgumentException("Invalid sketch name: " + key);
+ }
+
+ SketchStorage.SketchEntry se = sketches.getSketch(key);
+ JsonObject result;
+
+ switch (se.family) {
+ case UNION:
+ case HLL:
+ case CPC:
+ result = processDistinctQuery(query, se.family, se.sketch);
+ break;
+
+ case KLL:
+ result = processQuantilesQuery(query, se.family, se.sketch);
+ break;
+
+ case FREQUENCY:
+ result = processFrequencyQuery(query, se.family, se.sketch);
+ break;
+
+ case RESERVOIR:
+ case VAROPT:
+ result = processSamplingQuery(query, se.family, se.sketch);
+ break;
+
+ default:
+ throw new IllegalStateException("Unexpected sketch family: " + se.family);
+ }
+
+ return result;
+ }
+
+ private boolean checkSummaryFlag(JsonObject query) {
+ boolean addSummary = false;
+ if (query != null && query.has(QUERY_SUMMARY_FIELD)) {
+ addSummary = query.get(QUERY_SUMMARY_FIELD).getAsBoolean();
+ }
+ return addSummary;
+ }
+
+ private JsonObject processDistinctQuery(JsonObject query, Family type, Object sketch) {
+ if (query == null || type == null || sketch == null) {
+ return null;
+ }
+
+ // check if we need a summary
+ boolean addSummary = checkSummaryFlag(query);
+ String summary = null;
+
+ double estimate;
+ boolean isEstimationMode;
+ double p1StdDev;
+ double p2StdDev;
+ double p3StdDev;
+ double m1StdDev;
+ double m2StdDev;
+ double m3StdDev;
+
+ switch (type) {
+ case UNION:
+ CompactSketch thetaSketch = ((Union) sketch).getResult();
+ isEstimationMode = thetaSketch.isEstimationMode();
+ estimate = thetaSketch.getEstimate();
+ p1StdDev = thetaSketch.getUpperBound(1);
+ p2StdDev = thetaSketch.getUpperBound(2);
+ p3StdDev = thetaSketch.getUpperBound(3);
+ m1StdDev = thetaSketch.getLowerBound(1);
+ m2StdDev = thetaSketch.getLowerBound(2);
+ m3StdDev = thetaSketch.getLowerBound(3);
+ if (addSummary) { summary = thetaSketch.toString(); }
+ break;
+
+ case CPC:
+ CpcSketch cpcSketch = (CpcSketch) sketch;
+ isEstimationMode = true; // no exact mode
+ estimate = cpcSketch.getEstimate();
+ p1StdDev = cpcSketch.getUpperBound(1);
+ p2StdDev = cpcSketch.getUpperBound(2);
+ p3StdDev = cpcSketch.getUpperBound(3);
+ m1StdDev = cpcSketch.getLowerBound(1);
+ m2StdDev = cpcSketch.getLowerBound(2);
+ m3StdDev = cpcSketch.getLowerBound(3);
+ if (addSummary) { summary = cpcSketch.toString(); }
+ break;
+
+ case HLL:
+ HllSketch hllSketch = (HllSketch) sketch;
+ isEstimationMode = hllSketch.isEstimationMode();
+ estimate = hllSketch.getEstimate();
+ p1StdDev = hllSketch.getUpperBound(1);
+ p2StdDev = hllSketch.getUpperBound(2);
+ p3StdDev = hllSketch.getUpperBound(3);
+ m1StdDev = hllSketch.getLowerBound(1);
+ m2StdDev = hllSketch.getLowerBound(2);
+ m3StdDev = hllSketch.getLowerBound(3);
+ if (addSummary) { summary = hllSketch.toString(); }
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown distinct counting sketch type: " + type.toString());
+ }
+
+ JsonObject result = new JsonObject();
+ result.addProperty(RESPONSE_ESTIMATE_FIELD, estimate);
+ result.addProperty(RESPONSE_ESTIMATION_MODE_FIELD, isEstimationMode);
+ result.addProperty(RESPONSE_P1STDEV_FIELD, p1StdDev);
+ result.addProperty(RESPONSE_P2STDEV_FIELD, p2StdDev);
+ result.addProperty(RESPONSE_P3STDEV_FIELD, p3StdDev);
+ result.addProperty(RESPONSE_M1STDEV_FIELD, m1StdDev);
+ result.addProperty(RESPONSE_M2STDEV_FIELD, m2StdDev);
+ result.addProperty(RESPONSE_M3STDEV_FIELD, m3StdDev);
+ if (addSummary)
+ result.addProperty(RESPONSE_SUMMARY_FIELD, summary);
+
+ return result;
+ }
+
+ private JsonObject processQuantilesQuery(JsonObject query, Family type, Object sketch) {
+ if (query == null || type == null || sketch == null) {
+ return null;
+ }
+
+ // check if we need a summary
+ boolean addSummary = checkSummaryFlag(query);
+ String summary = null;
+
+ boolean isEstimationMode;
+ float maxValue;
+ float minValue;
+ long streamLength;
+
+ double[] fractions = getFractionsArray(query);
+ float[] quantiles = null;
+
+ float[] values = getValuesArray(query);
+ String resultType = getResultType(query);
+ double[] ranks = null;
+
+ // since we know REQ is coming
+ switch (type) {
+ case KLL:
+ KllFloatsSketch kll = (KllFloatsSketch) sketch;
+ isEstimationMode = kll.isEstimationMode();
+ maxValue = kll.getMaxValue();
+ minValue = kll.getMinValue();
+ streamLength = kll.getN();
+
+ if (values != null) {
+ ranks = resultType.equals(QUERY_RESULT_TYPE_CDF) ? kll.getCDF(values) : kll.getPMF(values);
+ }
+
+ if (fractions != null) {
+ quantiles = kll.getQuantiles(fractions);
+ }
+
+ if (addSummary)
+ summary = kll.toString();
+ break;
+
+ default:
+ throw new SketchesException("processQuantilesQuery() received a non-quantiles sketch: " + type.toString());
+ }
+
+ JsonObject result = new JsonObject();
+ result.addProperty(RESPONSE_STREAM_LENGTH, streamLength);
+ result.addProperty(RESPONSE_ESTIMATION_MODE_FIELD, isEstimationMode);
+ result.addProperty(RESPONSE_MIN_VALUE, minValue);
+ result.addProperty(RESPONSE_MAX_VALUE, maxValue);
+
+ if (ranks != null) {
+ final String label = resultType.equals(QUERY_RESULT_TYPE_PMF) ? RESPONSE_RESULT_MASS : RESPONSE_RESULT_RANK;
+ JsonArray rankArray = new JsonArray();
+ for (int i = 0; i < ranks.length; ++i) {
+ JsonObject rankPair = new JsonObject();
+ if (i == values.length) {
+ rankPair.addProperty(RESPONSE_RESULT_VALUE, maxValue);
+ } else {
+ rankPair.addProperty(RESPONSE_RESULT_VALUE, values[i]);
+ }
+ rankPair.addProperty(label, ranks[i]);
+ rankArray.add(rankPair);
+ }
+
+ if (resultType.equals(QUERY_RESULT_TYPE_CDF))
+ result.add(RESPONSE_CDF_LIST, rankArray);
+ else
+ result.add(RESPONSE_PMF_LIST, rankArray);
+ }
+
+ if (quantiles != null) {
+ JsonArray quantileArray = new JsonArray();
+ for (int i = 0; i < quantiles.length; ++i) {
+ JsonObject quantilePair = new JsonObject();
+ quantilePair.addProperty(RESPONSE_RESULT_RANK, fractions[i]);
+ quantilePair.addProperty(RESPONSE_RESULT_QUANTILE, quantiles[i]);
+ quantileArray.add(quantilePair);
+ }
+ result.add(RESPONSE_QUANTILE_LIST, quantileArray);
+ }
+
+ if (addSummary)
+ result.addProperty(RESPONSE_SUMMARY_FIELD, summary);
+
+ return result;
+ }
+
+ // only one sketch type here so could use ItemsSketch<String> instead of Object, but
+ // we'll eep the signatures generic here
+ @SuppressWarnings("unchecked")
+ private JsonObject processFrequencyQuery(JsonObject query, Family type, Object sketch) {
+ if (query == null || type != Family.FREQUENCY || sketch == null) {
+ return null;
+ }
+
+ ItemsSketch<String> sk = (ItemsSketch<String>) sketch;
+
+ // check if we need a summary
+ boolean addSummary = checkSummaryFlag(query);
+
+ if (!query.has(QUERY_ERRORTYPE_FIELD)) {
+ throw new SketchesException("Must specify a value for " + QUERY_ERRORTYPE_FIELD
+ + " for Frequent Items queries");
+ }
+
+ ErrorType errorType;
+ String errorTypeString = query.get(QUERY_ERRORTYPE_FIELD).getAsString();
+ if (errorTypeString.equals(QUERY_ERRORTYPE_NO_FP)) {
+ errorType = ErrorType.NO_FALSE_POSITIVES;
+ } else if (errorTypeString.equals(QUERY_ERRORTYPE_NO_FN)) {
+ errorType = ErrorType.NO_FALSE_NEGATIVES;
+ } else {
+ throw new SketchesException("Unknown Frequent Items ErrorType: " + errorTypeString);
+ }
+
+ ItemsSketch.Row<String>[] items = sk.getFrequentItems(errorType);
+
+ JsonArray itemArray = new JsonArray();
+ for (ItemsSketch.Row<String> item : items) {
+ JsonObject row = new JsonObject();
+ row.addProperty(RESPONSE_ITEM_VALUE, item.getItem());
+ row.addProperty(RESPONSE_ITEM_ESTIMATE, item.getEstimate());
+ row.addProperty(RESPONSE_ITEM_UPPER_BOUND, item.getUpperBound());
+ row.addProperty(RESPONSE_ITEM_LOWER_BOUND, item.getLowerBound());
+ itemArray.add(row);
+ }
+
+ JsonObject result = new JsonObject();
+ result.add(RESPONSE_ITEMS_ARRAY, itemArray);
+ if (addSummary)
+ result.addProperty(RESPONSE_SUMMARY_FIELD, sk.toString());
+
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private JsonObject processSamplingQuery(JsonObject query, Family type, Object sketch) {
+ if (query == null || type == null || sketch == null) {
+ return null;
+ }
+
+ // check if we need a summary
+ boolean addSummary = checkSummaryFlag(query);
+ String summary = null;
+
+ long streamWeight;
+ int k;
+ JsonArray itemArray = new JsonArray();
+
+ switch (type) {
+ case RESERVOIR:
+ ReservoirItemsSketch<String> ris = (ReservoirItemsSketch<String>) sketch;
+ for (String item : ris.getSamples()) {
+ itemArray.add(item);
+ }
+ streamWeight = ris.getN();
+ k = ris.getK();
+ if (addSummary)
+ summary = ris.toString();
+ break;
+
+ case VAROPT:
+ VarOptItemsSketch<String> vis = (VarOptItemsSketch<String>) sketch;
+ for (VarOptItemsSamples<String>.WeightedSample ws : vis.getSketchSamples()) {
+ JsonObject item = new JsonObject();
+ item.addProperty(RESPONSE_ITEM_VALUE, ws.getItem());
+ item.addProperty(RESPONSE_ITEM_WEIGHT, ws.getWeight());
+ itemArray.add(item);
+ }
+ streamWeight = vis.getN();
+ k = vis.getK();
+ if (addSummary)
+ summary = vis.toString();
+ break;
+
+ default:
+ throw new SketchesException("processSamplingQuery() received a non-sampling sketch: " + type.toString());
+ }
+
+ JsonObject result = new JsonObject();
+ result.addProperty(RESPONSE_SKETCH_K, k);
+ result.addProperty(RESPONSE_STREAM_WEIGHT, streamWeight);
+ result.add(RESPONSE_ITEMS_ARRAY, itemArray);
+ if (addSummary)
+ result.addProperty(RESPONSE_SUMMARY_FIELD, summary);
+
+ return result;
+ }
+
+ // returns an array of rank points, or null if none in query
+ private float[] getValuesArray(JsonObject query) {
+ if (query == null || !query.has(QUERY_VALUES_FIELD_NAME)) {
+ return null;
+ }
+
+ JsonArray valuesArray = query.get(QUERY_VALUES_FIELD_NAME).getAsJsonArray();
+ float[] values = new float[valuesArray.size()];
+
+ for (int i = 0; i < values.length; ++i) {
+ if (!valuesArray.get(i).isJsonPrimitive()) {
+ throw new SketchesException("Invalid value in array. Must be a floating point value, found: " + valuesArray.get(i));
+ }
+ values[i] = valuesArray.get(i).getAsFloat();
+ }
+
+ return values;
+ }
+
+ // returns QUERY_RESULT_TYPE_PMF if specified in QUERY_RESULT_TYPE_NAME_FIELD, otherwise returns default of
+ // QUERY_RESULT_TYPE_CDF
+ private String getResultType(JsonObject query) {
+ if (query == null || !query.has(QUERY_RESULT_TYPE_NAME_FIELD)) {
+ return QUERY_RESULT_TYPE_CDF;
+ }
+
+ JsonElement resultTypeElement = query.get(QUERY_RESULT_TYPE_NAME_FIELD);
+ if (!resultTypeElement.isJsonPrimitive())
+ return QUERY_RESULT_TYPE_CDF;
+
+ String resultType = resultTypeElement.getAsString().toLowerCase();
+ if (resultType.equals(QUERY_RESULT_TYPE_PMF))
+ return QUERY_RESULT_TYPE_PMF;
+ else
+ return QUERY_RESULT_TYPE_CDF;
+ }
+
+ // returns an array of provided split points, or null if none in query
+ private double[] getFractionsArray(JsonObject query) {
+ if (query == null || !query.has(QUERY_FRACTIONS_NAME_FIELD)) {
+ return null;
+ }
+
+ JsonArray fractionsArray = query.get(QUERY_FRACTIONS_NAME_FIELD).getAsJsonArray();
+ double[] fractions = new double[fractionsArray.size()];
+
+ for (int i = 0; i < fractions.length; ++i) {
+ if (!fractionsArray.get(i).isJsonPrimitive()) {
+ throw new SketchesException("Invalid value in array. Must be float in [0.0, 1.0], found: " + fractionsArray.get(i));
+ }
+ fractions[i] = fractionsArray.get(i).getAsFloat();
+ }
+
+ return fractions;
+ }
+
+}
diff --git a/src/main/java/org/apache/datasketches/server/MergeHandler.java b/src/main/java/org/apache/datasketches/server/MergeHandler.java
new file mode 100644
index 0000000..ecc07e1
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/MergeHandler.java
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashSet;
+
+import org.apache.datasketches.ArrayOfStringsSerDe;
+import org.apache.datasketches.Family;
+import org.apache.datasketches.SketchesException;
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.cpc.CpcUnion;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.memory.Memory;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsUnion;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsUnion;
+import org.apache.datasketches.theta.CompactSketch;
+import org.apache.datasketches.theta.SetOperationBuilder;
+import org.apache.datasketches.theta.Union;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * Performs a merge operation between sketches.
+ * <pre>
+ * {
+ * "target": "<destination_key>", // optional: return serialized result if missing
+ * "source": [ { "family": "<sketch_family>",
+ * "data": "<base64_encoded_sketch>"
+ * } ]
+ * }
+ * </pre>
+ * where <tt>source</tt> is an array of key names or {family, data} pairs. Inputs must be of the same family
+ * as the target. If no target is present, the family of the first input sketch is used instead. Merge order
+ * is not guaranteed.
+ */
+public class MergeHandler extends BaseSketchesQueryHandler {
+ MergeHandler(SketchStorage sketches) {
+ super(sketches);
+ }
+
+ @Override
+ protected JsonObject processQuery(JsonObject query) {
+ // optional targets:
+ // If no QUERY_MERGE_TGT_FIELD serialize the result, but then need specify QUERY_MERGE_K_FIELD.
+ // If a valid target is present, any value of QUERY_MERGE_K_FIELD is ignored
+ JsonElement dstElement = query.get(SketchConstants.QUERY_MERGE_TGT_FIELD);
+ String dst = dstElement != null ? dstElement.getAsString() : null;
+ if (dst != null && !sketches.contains(dst)) {
+ throw new IllegalArgumentException("Specified target sketch does not exist: " + dst);
+ }
+
+ int k = 0;
+ if (dst == null) {
+ JsonElement kElement = query.get(SketchConstants.QUERY_MERGE_K_FIELD);
+ if (kElement == null) {
+ throw new IllegalArgumentException("Must specify either \"" + SketchConstants.QUERY_MERGE_TGT_FIELD
+ + "\" or \"" + SketchConstants.QUERY_MERGE_K_FIELD + "\". Neither found.");
+ }
+ k = kElement.getAsInt();
+ }
+
+ JsonElement srcElement = query.get(SketchConstants.QUERY_MERGE_SRC_FIELD);
+ if (srcElement == null || !srcElement.isJsonArray()) {
+ throw new IllegalArgumentException("Merge source data must be a JSON Array");
+ }
+ JsonArray srcList = srcElement.getAsJsonArray();
+
+ SketchStorage.SketchEntry se = null;
+ Family dstFamily = null;
+ if (dst != null) {
+ se = sketches.getSketch(dst);
+ dstFamily = se.family;
+ }
+
+ // we'll process (and dedup) any stored sketches before we handle encoded inputs
+ // but we'll run through all of them before doing anything
+ ArrayList<Object> srcSketches = new ArrayList<>(srcList.size());
+
+ dstFamily = prepareSketches(srcList, dstFamily, dst, srcSketches);
+ byte[] skBytes = mergeSketches(dstFamily, k, se, srcSketches);
+
+ // skBytes == null if merging into another sketch; only non-null if returning a serialized image
+ if (skBytes != null) {
+ JsonObject result = new JsonObject();
+ result.addProperty(SketchConstants.QUERY_ENCODING_FIELD, SketchConstants.ENCODING_TYPE);
+ result.addProperty(SketchConstants.QUERY_SKETCH_FIELD, Base64.getEncoder().encodeToString(skBytes));
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ private Family prepareSketches(JsonArray sources, Family family, String dst, ArrayList<Object> sketchList) {
+ HashSet<String> namedSet = new HashSet<>();
+
+ // TODO: Check for sketch value types with distinct counting?
+ // Less obvious how to handle serialized sketch input in that case
+
+ // add destination if one exists just in case it's also listed as a source
+ if (dst != null) {
+ namedSet.add(dst);
+ }
+
+ for (JsonElement elmt : sources) {
+ if (elmt.isJsonPrimitive()) {
+ // check family
+ String key = elmt.getAsString();
+ SketchStorage.SketchEntry entry = sketches.getSketch(key);
+ if (entry == null || (family != null && family != entry.family)) {
+ throw new SketchesException("Input sketches must exist and be of the same family as the target");
+ }
+
+ // add to set, save family if we didn't have one yet
+ if (!namedSet.contains(key)) {
+ namedSet.add(key);
+ if (family == null) {
+ family = entry.family;
+ }
+
+ // if we have a theta Union we need to get the result first
+ if (entry.family == Family.UNION) {
+ sketchList.add(((Union) entry.sketch).getResult());
+ } else {
+ sketchList.add(entry.sketch);
+ }
+ }
+ } else { // is JsonObject
+ // need special handling for theta as we store Unions?
+ JsonObject sourceObj = elmt.getAsJsonObject();
+ if (!sourceObj.has(SketchConstants.QUERY_FAMILY_FIELD)
+ || !sourceObj.has(SketchConstants.QUERY_DATA_FIELD)) {
+ throw new SketchesException("Base64 sketch used as merge input must specify both \""
+ + SketchConstants.QUERY_FAMILY_FIELD + "\" and \"" + SketchConstants.QUERY_DATA_FIELD + "\"");
+ }
+
+ Family skFamily = familyFromString(sourceObj.get(SketchConstants.QUERY_FAMILY_FIELD).getAsString());
+ String skString = sourceObj.get(SketchConstants.QUERY_DATA_FIELD).getAsString();
+ if (skString == null || (family != null && family != skFamily)) {
+ throw new SketchesException("Input sketches must exist and be of the same family as the target");
+ }
+
+ // add to list, save family if we didn't have one yet
+ Object sketch = deserializeSketch(skFamily, skString);
+ sketchList.add(sketch);
+ if (family == null) {
+ family = skFamily;
+ }
+ }
+ }
+
+ return family;
+ }
+
+ private Object deserializeSketch(Family family, String b64String) {
+ if (family == null || b64String == null) {
+ return null;
+ }
+
+ Memory skBytes = Memory.wrap(Base64.getDecoder().decode(b64String));
+
+ switch (family) {
+ case QUICKSELECT:
+ return CompactSketch.heapify(skBytes);
+
+ case HLL:
+ return HllSketch.heapify(skBytes);
+
+ case CPC:
+ return CpcSketch.heapify(skBytes);
+
+ case KLL:
+ return KllFloatsSketch.heapify(skBytes);
+
+ case FREQUENCY:
+ return ItemsSketch.getInstance(skBytes, new ArrayOfStringsSerDe());
+
+ case RESERVOIR:
+ return ReservoirItemsSketch.heapify(skBytes, new ArrayOfStringsSerDe());
+
+ case VAROPT:
+ return VarOptItemsSketch.heapify(skBytes, new ArrayOfStringsSerDe());
+
+ default:
+ throw new SketchesException("Unsupported sketch family: " + family.toString());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private byte[] mergeSketches(Family family, int k, SketchStorage.SketchEntry dstEntry, ArrayList<Object> sketchList) {
+ if (family == null || sketchList.size() == 0) {
+ return null;
+ }
+
+ switch (family) {
+ case UNION:
+ case QUICKSELECT: {
+ // for HLL, the destination is already a union so no need to add explicitly
+ Union dst = dstEntry == null ? new SetOperationBuilder().setNominalEntries(1 << k).buildUnion()
+ : (Union) dstEntry.sketch;
+ for (Object obj : sketchList) {
+ dst.update((CompactSketch) obj);
+ }
+
+ if (dstEntry == null) {
+ return dst.getResult().toByteArray();
+ } else {
+ dstEntry.sketch = dst;
+ return null;
+ }
+ }
+
+ case HLL: {
+ org.apache.datasketches.hll.Union union = new org.apache.datasketches.hll.Union(k);
+ if (dstEntry != null) {
+ union.update((HllSketch) dstEntry.sketch);
+ }
+ for (Object obj : sketchList) {
+ union.update((HllSketch) obj);
+ }
+
+ if (dstEntry == null) {
+ return union.getResult().toCompactByteArray();
+ } else {
+ dstEntry.sketch = union.getResult();
+ return null;
+ }
+ }
+
+ case CPC: {
+ CpcUnion union = new CpcUnion(k);
+ if (dstEntry != null) {
+ union.update((CpcSketch) dstEntry.sketch);
+ }
+ for (Object obj : sketchList) {
+ union.update((CpcSketch) obj);
+ }
+
+ if (dstEntry == null) {
+ return union.getResult().toByteArray();
+ } else {
+ dstEntry.sketch = union.getResult();
+ return null;
+ }
+ }
+
+ case KLL: {
+ // Only merge(), no separate union. Slightly abusing terminology to call it union
+ KllFloatsSketch union = dstEntry == null ? new KllFloatsSketch(k) : (KllFloatsSketch) dstEntry.sketch;
+
+ for (Object obj : sketchList) {
+ union.merge((KllFloatsSketch) obj);
+ }
+
+ if (dstEntry == null) {
+ return union.toByteArray();
+ } else {
+ dstEntry.sketch = union;
+ return null;
+ }
+ }
+
+ case FREQUENCY: {
+ // Only merge(), no separate union. Slightly abusing terminology to call it union
+ ItemsSketch<String> union = dstEntry == null ? new ItemsSketch<>(k) : (ItemsSketch<String>) dstEntry.sketch;
+
+ for (Object obj : sketchList) {
+ union.merge((ItemsSketch<String>) obj);
+ }
+
+ if (dstEntry == null) {
+ return union.toByteArray(new ArrayOfStringsSerDe());
+ } else {
+ dstEntry.sketch = union;
+ return null;
+ }
+ }
+
+ case RESERVOIR: {
+ ReservoirItemsUnion<String> union = ReservoirItemsUnion.newInstance(k);
+ if (dstEntry != null) {
+ union.update((ReservoirItemsSketch<String>) dstEntry.sketch);
+ }
+
+ for (Object obj : sketchList) {
+ union.update((ReservoirItemsSketch<String>) obj);
+ }
+
+ if (dstEntry == null) {
+ return union.getResult().toByteArray(new ArrayOfStringsSerDe());
+ } else {
+ dstEntry.sketch = union.getResult();
+ return null;
+ }
+ }
+
+ case VAROPT: {
+ VarOptItemsUnion<String> union = VarOptItemsUnion.newInstance(k);
+ if (dstEntry != null) {
+ union.update((VarOptItemsSketch<String>) dstEntry.sketch);
+ }
+
+ for (Object obj : sketchList) {
+ union.update((VarOptItemsSketch<String>) obj);
+ }
+
+ if (dstEntry == null) {
+ return union.getResult().toByteArray(new ArrayOfStringsSerDe());
+ } else {
+ dstEntry.sketch = union.getResult();
+ return null;
+ }
+ }
+
+ default:
+ throw new SketchesException("Unsupported sketch family: " + family.toString());
+ }
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/SerializationHandler.java b/src/main/java/org/apache/datasketches/server/SerializationHandler.java
new file mode 100644
index 0000000..be78b4c
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/SerializationHandler.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import java.util.Base64;
+
+import org.apache.datasketches.ArrayOfStringsSerDe;
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.theta.Union;
+
+import com.google.gson.JsonObject;
+
+import static org.apache.datasketches.server.SketchConstants.*;
+
+/**
+ * Returns a serialized image of a sketch, encoded in base64, as well as the sketch family and, if relevant,
+ * the <tt>ValueType</tt> used.
+ * <pre>
+ * {
+ * "name": "<sketch_name>"
+ * }
+ * </pre>
+ */
+public class SerializationHandler extends BaseSketchesQueryHandler {
+ public SerializationHandler(SketchStorage sketches) {
+ super(sketches, false);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected JsonObject processQuery(JsonObject query) {
+ if (!query.has(QUERY_NAME_FIELD)) {
+ throw new IllegalArgumentException("Query missing sketch name field");
+ }
+
+ String name = query.get(QUERY_NAME_FIELD).getAsString();
+ if (!sketches.contains(name)) {
+ throw new IllegalArgumentException("Invalid sketch name: " + name);
+ }
+
+ SketchStorage.SketchEntry se = sketches.getSketch(name);
+
+ byte[] bytes;
+ switch (se.family) {
+ case UNION:
+ bytes = ((Union) se.sketch).getResult().toByteArray();
+ break;
+ case KLL:
+ bytes = ((KllFloatsSketch) se.sketch).toByteArray();
+ break;
+ case FREQUENCY:
+ bytes = ((ItemsSketch<String>) se.sketch).toByteArray(new ArrayOfStringsSerDe());
+ break;
+ case HLL:
+ bytes = ((HllSketch) se.sketch).toCompactByteArray();
+ break;
+ case CPC:
+ bytes = ((CpcSketch) se.sketch).toByteArray();
+ break;
+ case RESERVOIR:
+ bytes = ((ReservoirItemsSketch<String>) se.sketch).toByteArray(new ArrayOfStringsSerDe());
+ break;
+ case VAROPT:
+ bytes = ((VarOptItemsSketch<String>) se.sketch).toByteArray(new ArrayOfStringsSerDe());
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + se.family);
+ }
+
+ String b64Sketch = Base64.getEncoder().encodeToString(bytes);
+
+ JsonObject result = new JsonObject();
+ result.addProperty(QUERY_NAME_FIELD, name);
+ result.addProperty(CONFIG_FAMILY_FIELD, se.family.getFamilyName());
+ if (se.type != null)
+ result.addProperty(CONFIG_TYPE_FIELD, se.type.getTypeName());
+ result.addProperty(QUERY_ENCODING_FIELD, ENCODING_TYPE);
+ result.addProperty(QUERY_SKETCH_FIELD, b64Sketch);
+
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/SketchConstants.java b/src/main/java/org/apache/datasketches/server/SketchConstants.java
new file mode 100644
index 0000000..dc04a2f
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/SketchConstants.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+public final class SketchConstants {
+ // API call paths, relative to root
+ public static final String UPDATE_PATH = "/update";
+ public static final String SERIALIZE_PATH = "/serialize";
+ public static final String STATUS_PATH = "/status";
+ public static final String QUERY_PATH = "/query";
+ public static final String MERGE_PATH = "/merge";
+
+ // JSON Query/Update/Merge Field Names
+ public static final String QUERY_NAME_FIELD = "name";
+ public static final String QUERY_FAMILY_FIELD = "family";
+ public static final String QUERY_ENCODING_FIELD = "encoding";
+ public static final String QUERY_SKETCH_FIELD = "sketch";
+ public static final String QUERY_DATA_FIELD = "data";
+ public static final String QUERY_PAIR_ITEM_FIELD = "item";
+ public static final String QUERY_PAIR_WEIGHT_FIELD = "weight";
+ public static final String QUERY_MERGE_TGT_FIELD = "target";
+ public static final String QUERY_MERGE_SRC_FIELD = "source";
+ public static final String QUERY_MERGE_K_FIELD = "k";
+ public static final String QUERY_SUMMARY_FIELD = "summary";
+ public static final String QUERY_ERRORTYPE_FIELD = "errorType";
+ public static final String QUERY_ERRORTYPE_NO_FP = "noFalsePositives";
+ public static final String QUERY_ERRORTYPE_NO_FN = "noFalseNegatives";
+ public static final String QUERY_VALUES_FIELD_NAME = "values";
+ public static final String QUERY_FRACTIONS_NAME_FIELD = "fractions";
+ public static final String QUERY_RESULT_TYPE_NAME_FIELD = "resultType";
+ public static final String QUERY_RESULT_TYPE_PMF = "pmf";
+ public static final String QUERY_RESULT_TYPE_CDF = "cdf";
+
+ // JSON Query Response Field Names
+ public static final String RESPONSE_SUMMARY_FIELD = QUERY_SUMMARY_FIELD;
+ public static final String RESPONSE_ESTIMATE_FIELD = "estimate";
+ public static final String RESPONSE_ESTIMATION_MODE_FIELD = "estimationMode";
+ public static final String RESPONSE_P1STDEV_FIELD = "plus1StdDev";
+ public static final String RESPONSE_P2STDEV_FIELD = "plus2StdDev";
+ public static final String RESPONSE_P3STDEV_FIELD = "plus3StdDev";
+ public static final String RESPONSE_M1STDEV_FIELD = "minus1StdDev";
+ public static final String RESPONSE_M2STDEV_FIELD = "minus2StdDev";
+ public static final String RESPONSE_M3STDEV_FIELD = "minus3StdDev";
+ public static final String RESPONSE_ITEMS_ARRAY = "items";
+ public static final String RESPONSE_ITEM_VALUE = "item";
+ public static final String RESPONSE_ITEM_ESTIMATE = "estimate";
+ public static final String RESPONSE_ITEM_UPPER_BOUND = "upperBound";
+ public static final String RESPONSE_ITEM_LOWER_BOUND = "lowerBound";
+ public static final String RESPONSE_STREAM_WEIGHT = "streamWeight"; // used for sampling
+ public static final String RESPONSE_SKETCH_K = "sketchK";
+ public static final String RESPONSE_ITEM_WEIGHT = "weight";
+ public static final String RESPONSE_STREAM_LENGTH = "streamLength";
+ public static final String RESPONSE_MAX_VALUE = "maxValue";
+ public static final String RESPONSE_MIN_VALUE = "minValue";
+ public static final String RESPONSE_CDF_LIST = "estimatedCDF";
+ public static final String RESPONSE_PMF_LIST = "estimatedPMF";
+ public static final String RESPONSE_RESULT_VALUE = "value";
+ public static final String RESPONSE_RESULT_RANK = "rank";
+ public static final String RESPONSE_RESULT_MASS = "mass";
+ public static final String RESPONSE_QUANTILE_LIST = "estimatedQuantiles";
+ public static final String RESPONSE_RESULT_QUANTILE = "quantile";
+
+ // JSON Config Field Names
+ public static final String CONFIG_PORT_FIELD = "port";
+ public static final String CONFIG_SKETCHES_PREFIX = "sketches"; // >= 1 fully described sketches
+ public static final String CONFIG_SET_PREFIX = "set";
+ public static final String CONFIG_K_FIELD = "k";
+ public static final String CONFIG_FAMILY_FIELD = "family";
+ public static final String CONFIG_TYPE_FIELD = "type"; // value type, only for distinct counting
+ public static final String CONFIG_SKETCH_NAME_FIELD = "name";
+ public static final String CONFIG_SET_NAMES_FIELD = "names";
+
+ // JSON Sketch Types
+ public static final String SKETCH_FAMILY_THETA = "theta";
+ public static final String SKETCH_FAMILY_HLL = "hll";
+ public static final String SKETCH_FAMILY_CPC = "cpc";
+ public static final String SKETCH_FAMILY_FREQUENCY = "frequency";
+ public static final String SKETCH_FAMILY_KLL = "kll";
+ public static final String SKETCH_FAMILY_RESERVOIR = "reservoir";
+ public static final String SKETCH_FAMILY_VAROPT = "varopt";
+
+ // JSON Value Types (applicable only for distinct counting)
+ public static final String VALUE_TYPE_INT = "int";
+ public static final String VALUE_TYPE_LONG = "long";
+ public static final String VALUE_TYPE_FLOAT = "float";
+ public static final String VALUE_TYPE_DOUBLE = "double";
+ public static final String VALUE_TYPE_STRING = "string";
+
+ // server configuration
+ public static final int DEFAULT_PORT = 8080;
+
+ // response codes
+ public static final int UNPROCESSABLE_ENTITY = 422; // defined, but not in HttpServletResponse.SC_* codes
+
+ public static final String ERROR_KEY = "error";
+
+ public static final String ENCODING_TYPE = "base64";
+}
diff --git a/src/main/java/org/apache/datasketches/server/SketchServer.java b/src/main/java/org/apache/datasketches/server/SketchServer.java
new file mode 100644
index 0000000..eed38c4
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/SketchServer.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+
+/**
+ * Creates a very basic sketch server running embedded Jetty. Configuration options are specified in a config
+ * file; for details @see SketchServerConfig.
+ */
+public class SketchServer {
+ private final SketchServerConfig config;
+ private SketchStorage sketches;
+ private Server server;
+
+ /**
+ * Creates a server with the provided configuration
+ * @param configFile Path to a configuration file following the <tt>SketchServerConfig</tt> format
+ * @throws IOException
+ */
+ public SketchServer(final String configFile) throws IOException {
+ config = new SketchServerConfig(configFile);
+ }
+
+ // defines paths and registers the relevant handlers
+ private void createServer() {
+ server = new Server(config.getPort());
+
+ // Error page unless you have a correct URL
+ ContextHandler contextRoot = new ContextHandler("/");
+ contextRoot.setContextPath("/");
+ contextRoot.setHandler(new ErrorHandler());
+
+ ContextHandler contextStatus = new ContextHandler(SketchConstants.STATUS_PATH);
+ contextStatus.setHandler(new StatusHandler(sketches));
+
+ ContextHandler contextSerialize = new ContextHandler(SketchConstants.SERIALIZE_PATH);
+ contextSerialize.setHandler(new SerializationHandler(sketches));
+
+ ContextHandler contextUpdate = new ContextHandler(SketchConstants.UPDATE_PATH);
+ contextUpdate.setHandler(new UpdateHandler(sketches));
+
+ ContextHandler contextMerge = new ContextHandler(SketchConstants.MERGE_PATH);
+ contextMerge.setHandler(new MergeHandler(sketches));
+
+ ContextHandler contextQuery = new ContextHandler(SketchConstants.QUERY_PATH);
+ contextQuery.setHandler(new DataQueryHandler(sketches));
+
+ ContextHandlerCollection contexts =
+ new ContextHandlerCollection(contextRoot,
+ contextStatus,
+ contextSerialize,
+ contextUpdate,
+ contextMerge,
+ contextQuery);
+ server.setHandler(contexts);
+ }
+
+ /**
+ * Initializes the sketches, configures query handlers, and Starts the server.
+ * @throws Exception
+ */
+ public void start() throws Exception {
+ sketches = new SketchStorage(config.getSketchList());
+ createServer();
+ server.start();
+ }
+
+ /**
+ * Returns the server's base URI
+ * @return Server URI as a string
+ */
+ public String getURI() {
+ if (server == null)
+ return null;
+ return server.getURI().toString();
+ }
+
+ /**
+ * Returns the server's configured port
+ * @return The server's pert
+ */
+ public int getPort() {
+ if (server != null) {
+ return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
+ }
+ return -1;
+ }
+
+ public static void main(String[] args) throws Exception {
+ //String confFile = args[0];
+ String confFile = "conf3.json";
+
+ SketchServer sketchServer = new SketchServer(confFile);
+ sketchServer.start();
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/SketchServerConfig.java b/src/main/java/org/apache/datasketches/server/SketchServerConfig.java
new file mode 100644
index 0000000..8a1a4c5
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/SketchServerConfig.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import static org.apache.datasketches.server.SketchStorage.isDistinctCounting;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import static org.apache.datasketches.server.SketchConstants.*;
+
+/**
+ * A class to hold the server configuration, along with a supporting subclass and file-parsing methods.
+ *
+ * TODO: define config options
+ */
+class SketchServerConfig {
+ public static class SketchInfo {
+ public String name;
+ public int k;
+ public String family;
+ public String type;
+
+ SketchInfo(String name, int k, String family, String type) {
+ this.name = name;
+ this.k = k;
+ this.family = family;
+ this.type = type;
+ }
+
+ SketchInfo(String name, int k, String family) {
+ this.name = name;
+ this.k = k;
+ this.family = family;
+ this.type = null;
+ }
+
+ }
+
+ private int port = SketchConstants.DEFAULT_PORT;
+ private ArrayList<SketchInfo> sketchList;
+
+ SketchServerConfig(String configFile) throws IOException {
+ JsonElement config = readJsonFromFile(configFile);
+ parseConfig(config);
+ }
+
+ SketchServerConfig(JsonElement config) throws IOException {
+ parseConfig(config);
+ }
+
+ int getPort() {
+ return port;
+ }
+
+ List<SketchInfo> getSketchList() {
+ return sketchList;
+ }
+
+ // output should have a list with full info per sketch, even if input allows a
+ // more condensed format
+ private JsonElement readJsonFromFile(String configFile) {
+ JsonElement config = null;
+
+ try {
+ Reader reader = Files.newBufferedReader(Paths.get(configFile));
+ config = JsonParser.parseReader(reader);
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return config;
+ }
+
+ private void parseConfig(JsonElement config) throws IOException {
+ Gson gson = new Gson();
+
+ sketchList = new ArrayList<>();
+
+ if (config.isJsonArray()) {
+ // must be a list of fully-described sketches
+ sketchList.addAll(Arrays.asList(gson.fromJson(config.getAsJsonArray(), SketchInfo[].class)));
+ } else if (config.isJsonObject()) {
+ JsonObject confEntry = config.getAsJsonObject();
+ for (String name : confEntry.keySet()) {
+ if (name.toLowerCase().equals(CONFIG_PORT_FIELD)) {
+ // port the server should use
+ port = confEntry.get(name).getAsInt();
+ }
+ else if (name.toLowerCase().startsWith(CONFIG_SKETCHES_PREFIX)) {
+ // sketches* is an array of fully qualified sketches
+ sketchList.addAll(Arrays.asList(gson.fromJson(confEntry.get(name).getAsJsonArray(), SketchInfo[].class)));
+ } else if (name.toLowerCase().startsWith(CONFIG_SET_PREFIX)) {
+ // set* has a common name and type with an array of name names
+ JsonObject sketchSetInfo = confEntry.get(name).getAsJsonObject();
+ int k = sketchSetInfo.get(CONFIG_K_FIELD).getAsInt();
+ String family = sketchSetInfo.get(CONFIG_FAMILY_FIELD).getAsString();
+ String type = null;
+ if (isDistinctCounting(BaseSketchesQueryHandler.familyFromString(family))) {
+ type = sketchSetInfo.get(CONFIG_TYPE_FIELD).getAsString();
+ }
+ String[] nameList = gson.fromJson(sketchSetInfo.get(CONFIG_SET_NAMES_FIELD).getAsJsonArray(), String[].class);
+
+ for (String n : nameList)
+ sketchList.add(new SketchInfo(n, k, family, type));
+ }
+ }
+ } else {
+ throw new IOException("Expected JsonArray or JsonObject but none found");
+ }
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/SketchStorage.java b/src/main/java/org/apache/datasketches/server/SketchStorage.java
new file mode 100644
index 0000000..dd4b1ed
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/SketchStorage.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.datasketches.Family;
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.theta.SetOperationBuilder;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import static org.apache.datasketches.server.SketchConstants.*;
+
+/**
+ * A storage class for holding sketches. Each sketch must have a unique name, and be instantiated with one
+ * of the known types. For some sketch families, a <tt>ValueType</tt> (@see ValueType) may be required in
+ * order to ensure that data is presented in a consistent way.
+ */
+public class SketchStorage {
+ private static final String SKETCH_COUNT_NAME = "count";
+
+ /**
+ * Returns true if the sketch family is for distinct counting.
+ * @param family A sketch Family (@see org.apache.datasketches.Family)
+ * @return <tt>true</tt>e for distinct counting sketch families, otherwise <tt>false</tt>>.
+ */
+ static boolean isDistinctCounting(final Family family) {
+ return family == Family.QUICKSELECT || family == Family.UNION || family == Family.HLL || family == Family.CPC;
+ }
+
+ static class SketchEntry {
+ public final Family family;
+ public final ValueType type;
+ public Object sketch;
+
+ SketchEntry(Family family, ValueType type, Object sketch) throws IllegalArgumentException {
+ if (isDistinctCounting(family) && type == null)
+ throw new IllegalArgumentException("Must specify a value type for distinct counting sketches");
+
+ this.family = family;
+ this.type = type;
+ this.sketch = sketch;
+ }
+
+ SketchEntry(Family family, Object sketch) throws IllegalArgumentException {
+ if (isDistinctCounting(family))
+ throw new IllegalArgumentException("Must specify a value type for distinct counting sketches");
+
+ this.family = family;
+ this.type = null;
+ this.sketch = sketch;
+ }
+ }
+
+ HashMap<String, SketchEntry> sketchMap;
+
+ SketchStorage(List<SketchServerConfig.SketchInfo> sketchList) {
+ if (sketchList != null) {
+ createSketches(sketchList);
+ }
+ }
+
+ JsonObject listSketches() {
+ JsonObject summary = new JsonObject();
+
+ JsonArray sketchList = new JsonArray(sketchMap.size());
+ for (Map.Entry<String, SketchEntry> e : sketchMap.entrySet()) {
+ JsonObject item = new JsonObject();
+ item.addProperty(CONFIG_SKETCH_NAME_FIELD, e.getKey());
+ switch (e.getValue().family) {
+ case UNION:
+ item.addProperty(CONFIG_TYPE_FIELD, e.getValue().type.getTypeName());
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_THETA);
+ break;
+ case CPC:
+ item.addProperty(CONFIG_TYPE_FIELD, e.getValue().type.getTypeName());
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_CPC);
+ break;
+ case HLL:
+ item.addProperty(CONFIG_TYPE_FIELD, e.getValue().type.getTypeName());
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_HLL);
+ break;
+ case FREQUENCY:
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_FREQUENCY);
+ break;
+ case KLL:
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_KLL);
+ break;
+ case RESERVOIR:
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_RESERVOIR);
+ break;
+ case VAROPT:
+ item.addProperty(CONFIG_FAMILY_FIELD, SKETCH_FAMILY_VAROPT);
+ break;
+ }
+ sketchList.add(item);
+ }
+
+ summary.addProperty(SKETCH_COUNT_NAME, sketchMap.size());
+ summary.add(SketchConstants.CONFIG_SKETCHES_PREFIX, sketchList); // bare prefix, sketches fully qualified
+
+ return summary;
+ }
+
+ boolean contains(String key) {
+ return sketchMap.containsKey(key);
+ }
+
+ SketchEntry getSketch(String key) {
+ return sketchMap.get(key);
+ }
+
+ // instantiate the actual sketches, throwing if there's a duplicate key
+ private void createSketches(List<SketchServerConfig.SketchInfo> list) throws IllegalArgumentException {
+ sketchMap = new HashMap<>(list.size());
+
+ for (SketchServerConfig.SketchInfo info : list) {
+ if (sketchMap.containsKey(info.name)) {
+ throw new IllegalArgumentException("Duplicate sketch key: " + info.name);
+ }
+
+ SketchEntry sketchEntry = null;
+ Family family = BaseSketchesQueryHandler.familyFromString(info.family);
+
+ switch (family) {
+ case QUICKSELECT:
+ // make a Union so we can handle merges later
+ sketchEntry = new SketchEntry(Family.UNION, ValueType.stringToType(info.type),
+ new SetOperationBuilder().setNominalEntries(1 << info.k).buildUnion());
+ break;
+
+ case HLL:
+ sketchEntry = new SketchEntry(Family.HLL, ValueType.stringToType(info.type),
+ new HllSketch(info.k));
+ break;
+
+ case CPC:
+ sketchEntry = new SketchEntry(Family.CPC, ValueType.stringToType(info.type),
+ new CpcSketch(info.k));
+ break;
+
+ case KLL:
+ sketchEntry = new SketchEntry(Family.KLL, new KllFloatsSketch(info.k));
+ break;
+
+ case FREQUENCY:
+ sketchEntry = new SketchEntry(Family.FREQUENCY, new ItemsSketch<String>(info.k));
+ break;
+
+ case RESERVOIR:
+ sketchEntry = new SketchEntry(Family.RESERVOIR, ReservoirItemsSketch.<String>newInstance(info.k));
+ break;
+
+ case VAROPT:
+ sketchEntry = new SketchEntry(Family.VAROPT, VarOptItemsSketch.<String>newInstance(info.k));
+ break;
+ }
+
+ if (sketchEntry != null) {
+ sketchMap.put(info.name, sketchEntry);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/StatusHandler.java b/src/main/java/org/apache/datasketches/server/StatusHandler.java
new file mode 100644
index 0000000..d782a12
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/StatusHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import com.google.gson.JsonObject;
+
+/**
+ * Returns a JSON list of sketches held by the server and their types.
+ */
+public class StatusHandler extends BaseSketchesQueryHandler {
+
+ public StatusHandler(SketchStorage sketches) {
+ super(sketches, true);
+ }
+
+ @Override
+ protected JsonObject processQuery(JsonObject query) {
+ return sketches.listSketches();
+ }
+}
diff --git a/src/main/java/org/apache/datasketches/server/UpdateHandler.java b/src/main/java/org/apache/datasketches/server/UpdateHandler.java
new file mode 100644
index 0000000..2cc362f
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/UpdateHandler.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import java.util.Map;
+
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.frequencies.ItemsSketch;
+import org.apache.datasketches.hll.HllSketch;
+import org.apache.datasketches.kll.KllFloatsSketch;
+import org.apache.datasketches.sampling.ReservoirItemsSketch;
+import org.apache.datasketches.sampling.VarOptItemsSketch;
+import org.apache.datasketches.theta.Union;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import static org.apache.datasketches.server.SketchConstants.*;
+
+/**
+ *
+ * <p>
+ * Update format (single- and multi-value updates):
+ * <pre>
+ * {
+ * "<sketch_name_1>": <value>,
+ * "<sketch_name_2>": [<value_1>, <value_2>, ...].
+ * ...
+ * }
+ * </pre>
+ * where the type of each value is determined by the sketch configuration at server initialization.
+ * </p>
+ * <p>
+ * Each <em>value</em> may be a stand-alone item or, for sketches accepting weighted inputs, may be a JSON
+ * object: <tt>{ "item": <value>, "weight": <weight> }</tt>. Attempting to use a weight with a
+ * sketch that does not support weighted inputs will result in an error. The multi-value update format
+ * may contain a mux of weighted and unweighted items, as lonig s the sketch accepts weighted values.
+ * </p>
+ * <p>
+ * The JSON parsing library does not allow duplicate key names; to send multiple values into the same sketch
+ * in a single request, the multi-value option <em>must</em> be used.
+ * </p>
+ * <p>
+ * This is not a transactional database: Entries are processed and committed sequentially, returning an error
+ * if one is encountered at any point in the process. Any updates processed prior to the error will be
+ * retained by the server.
+ * </p>
+ */
+public class UpdateHandler extends BaseSketchesQueryHandler {
+ public UpdateHandler(SketchStorage sketches) {
+ super(sketches);
+ }
+
+ @Override
+ protected JsonObject processQuery(JsonObject query) {
+ for (Map.Entry<String, JsonElement> entry : query.entrySet()) {
+ String name = entry.getKey();
+ SketchStorage.SketchEntry se = sketches.getSketch(name);
+ JsonElement data = entry.getValue();
+
+ if (name == null || se == null || data == null) {
+ throw new IllegalArgumentException("Attempt to call update with missing name or sketch not found");
+ }
+
+ if (data.isJsonArray()) {
+ processBatchUpdate(se, data.getAsJsonArray());
+ } else {
+ processSingleUpdate(se, data);
+ }
+ }
+
+ // nothing to return from update
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void processBatchUpdate(SketchStorage.SketchEntry entry,
+ JsonArray data) {
+ switch (entry.family) {
+ case UNION: // theta
+ assert(entry.type != null);
+ switch (entry.type) {
+ case FLOAT: case DOUBLE:
+ for (JsonElement e : data) { ((Union) entry.sketch).update(e.getAsDouble()); }
+ break;
+ case INT: case LONG:
+ for (JsonElement e : data) { ((Union) entry.sketch).update(e.getAsLong()); }
+ break;
+ case STRING: default:
+ for (JsonElement e : data) { ((Union) entry.sketch).update(e.getAsString()); }
+ break;
+ }
+ break;
+
+ case CPC:
+ assert(entry.type != null);
+ switch (entry.type) {
+ case FLOAT: case DOUBLE:
+ for (JsonElement e : data) { ((CpcSketch) entry.sketch).update(e.getAsDouble()); }
+ break;
+ case INT: case LONG:
+ for (JsonElement e : data) { ((CpcSketch) entry.sketch).update(e.getAsLong()); }
+ break;
+ case STRING: default:
+ for (JsonElement e : data) { ((CpcSketch) entry.sketch).update(e.getAsString()); }
+ break;
+ }
+ break;
+
+ case HLL:
+ assert(entry.type != null);
+ switch (entry.type) {
+ case FLOAT: case DOUBLE:
+ for (JsonElement e : data) { ((HllSketch) entry.sketch).update(e.getAsDouble()); }
+ break;
+ case INT: case LONG:
+ for (JsonElement e : data) { ((CpcSketch) entry.sketch).update(e.getAsLong()); }
+ break;
+ case STRING: default:
+ for (JsonElement e : data) { ((CpcSketch) entry.sketch).update(e.getAsString()); }
+ break;
+ }
+ break;
+
+ case KLL:
+ for (JsonElement e : data) { ((KllFloatsSketch) entry.sketch).update(e.getAsFloat()); }
+ break;
+
+ case FREQUENCY:
+ for (JsonElement e : data) {
+ if (e.isJsonObject()) {
+ JsonObject inputPair = e.getAsJsonObject();
+ if (!inputPair.has(QUERY_PAIR_ITEM_FIELD) || !inputPair.has(QUERY_PAIR_WEIGHT_FIELD)) {
+ throw new IllegalArgumentException("Frequent Items input pairs must include both "
+ + QUERY_PAIR_ITEM_FIELD + " and " + QUERY_PAIR_WEIGHT_FIELD + " values");
+ }
+ String item = inputPair.get(QUERY_PAIR_ITEM_FIELD).getAsString();
+ int weight = inputPair.get(QUERY_PAIR_WEIGHT_FIELD).getAsInt();
+ ((ItemsSketch<String>) entry.sketch).update(item, weight);
+ } else {
+ ((ItemsSketch<String>) entry.sketch).update(e.getAsString());
+ }
+ }
+ break;
+
+ case RESERVOIR:
+ for (JsonElement e : data) { ((ReservoirItemsSketch<String>) entry.sketch).update(e.getAsString()); }
+ break;
+
+ case VAROPT:
+ for (JsonElement e : data) {
+ if (e.isJsonObject()) {
+ JsonObject inputPair = e.getAsJsonObject();
+ if (!inputPair.has(QUERY_PAIR_ITEM_FIELD) || !inputPair.has(QUERY_PAIR_WEIGHT_FIELD)) {
+ throw new IllegalArgumentException("VarOpt input pairs must include both "
+ + QUERY_PAIR_ITEM_FIELD + " and " + QUERY_PAIR_WEIGHT_FIELD + " values");
+ }
+ String item = inputPair.get(QUERY_PAIR_ITEM_FIELD).getAsString();
+ double weight = inputPair.get(QUERY_PAIR_WEIGHT_FIELD).getAsDouble();
+ ((VarOptItemsSketch<String>) entry.sketch).update(item, weight);
+ } else {
+ ((VarOptItemsSketch<String>) entry.sketch).update(e.getAsString(), 1.0);
+ }
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unsupported sketch type: " + entry.family);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void processSingleUpdate(SketchStorage.SketchEntry entry,
+ JsonElement data) {
+ switch (entry.family) {
+ case UNION:
+ assert(entry.type != null);
+ switch (entry.type) {
+ case FLOAT: case DOUBLE:
+ ((Union) entry.sketch).update(data.getAsDouble());
+ break;
+ case INT: case LONG:
+ ((Union) entry.sketch).update(data.getAsLong());
+ break;
+ case STRING: default:
+ ((Union) entry.sketch).update(data.getAsString());
+ break;
+ }
+ break;
+
+ case CPC:
+ assert(entry.type != null);
+ switch (entry.type) {
+ case FLOAT: case DOUBLE:
+ ((CpcSketch) entry.sketch).update(data.getAsDouble());
+ break;
+ case INT: case LONG:
+ ((CpcSketch) entry.sketch).update(data.getAsLong());
+ break;
+ case STRING: default:
+ ((CpcSketch) entry.sketch).update(data.getAsString());
+ break;
+ }
+ break;
+
+ case HLL:
+ assert(entry.type != null);
+ switch (entry.type) {
+ case FLOAT: case DOUBLE:
+ ((HllSketch) entry.sketch).update(data.getAsDouble());
+ break;
+ case INT: case LONG:
+ ((HllSketch) entry.sketch).update(data.getAsLong());
+ break;
+ case STRING: default:
+ ((HllSketch) entry.sketch).update(data.getAsString());
+ break;
+ }
+ break;
+
+ case KLL:
+ ((KllFloatsSketch) entry.sketch).update(data.getAsFloat());
+ break;
+
+ case FREQUENCY:
+ if (data.isJsonObject()) {
+ JsonObject inputPair = data.getAsJsonObject();
+ if (!inputPair.has(QUERY_PAIR_ITEM_FIELD) || !inputPair.has(QUERY_PAIR_WEIGHT_FIELD)) {
+ throw new IllegalArgumentException("Frequent Items input pairs must include both "
+ + QUERY_PAIR_ITEM_FIELD + " and " + QUERY_PAIR_WEIGHT_FIELD + " values");
+ }
+ String item = inputPair.get(QUERY_PAIR_ITEM_FIELD).getAsString();
+ int weight = inputPair.get(QUERY_PAIR_WEIGHT_FIELD).getAsInt();
+ ((ItemsSketch<String>) entry.sketch).update(item, weight);
+ } else {
+ ((ItemsSketch<String>) entry.sketch).update(data.getAsString());
+ }
+ break;
+
+ case RESERVOIR:
+ ((ReservoirItemsSketch<String>) entry.sketch).update(data.getAsString());
+ break;
+
+ case VAROPT:
+ if (data.isJsonObject()) {
+ JsonObject inputPair = data.getAsJsonObject();
+ if (!inputPair.has(QUERY_PAIR_ITEM_FIELD) || !inputPair.has(QUERY_PAIR_WEIGHT_FIELD)) {
+ throw new IllegalArgumentException("VarOpt input pairs must include both "
+ + QUERY_PAIR_ITEM_FIELD + " and " + QUERY_PAIR_WEIGHT_FIELD + " values");
+ }
+ String item = inputPair.get(QUERY_PAIR_ITEM_FIELD).getAsString();
+ double weight = inputPair.get(QUERY_PAIR_WEIGHT_FIELD).getAsDouble();
+ ((VarOptItemsSketch<String>) entry.sketch).update(item, weight);
+ } else {
+ ((VarOptItemsSketch<String>) entry.sketch).update(data.getAsString(), 1.0);
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unsupported sketch type: " + entry.family);
+ }
+ }
+
+}
+
diff --git a/src/main/java/org/apache/datasketches/server/ValueType.java b/src/main/java/org/apache/datasketches/server/ValueType.java
new file mode 100644
index 0000000..0ec1ff9
--- /dev/null
+++ b/src/main/java/org/apache/datasketches/server/ValueType.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.datasketches.server;
+
+import java.util.HashMap;
+
+/**
+ * An enum to hold the known data value types.
+ */
+public enum ValueType {
+
+ /**
+ * Character string. Uses default encoding.
+ */
+ STRING(SketchConstants.VALUE_TYPE_STRING),
+
+ /**
+ * 32-bit floating point value
+ */
+ FLOAT(SketchConstants.VALUE_TYPE_FLOAT),
+
+ /**
+ * 64-bit floating point value
+ */
+ DOUBLE(SketchConstants.VALUE_TYPE_DOUBLE),
+
+ /**
+ * 32-bit signed integer value
+ */
+ INT(SketchConstants.VALUE_TYPE_INT),
+
+ /**
+ * 64-bit signed integer value
+ */
+ LONG(SketchConstants.VALUE_TYPE_LONG);
+
+
+ private static final HashMap<String, ValueType> lookupTypeName = new HashMap<>();
+ private final String typeName_;
+
+ static {
+ for (ValueType t : values()) {
+ lookupTypeName.put(t.getTypeName().toLowerCase(), t);
+ }
+ }
+
+ ValueType(final String typeName) {
+ typeName_ = typeName;
+ }
+
+ public String getTypeName() {
+ return typeName_;
+ }
+
+ /**
+ * Returns the ValueType given the type name
+ * @param typeName the family name
+ * @return the ValueType given the type name
+ */
+ public static ValueType stringToType(final String typeName) {
+ final ValueType t = lookupTypeName.get(typeName.toLowerCase());
+ if (t == null) {
+ throw new IllegalArgumentException("Illegal ValueType Name: " + typeName);
+ }
+ return t;
+ }
+
+
+}