Permalink
Browse files

rfe10197: support for encodable namespaces and generated URIs

<release-note>
rfe10197: support for encodable namespaces and generated URIs

With this change, a namespace (viewed as a set of URIs) can now be
registered as encodable.  URIs in the namespace have local parts that
follow a declared format, allowing for efficient encoding and in some
cases generation of unique URIs in the namespace.  Once registered,
a conformant URI in the namespace is auto-encoded when added to the
store, resulting in a smaller string table and faster query speed.
</release-note>

Added EncodableNamespaceTests to prepush tests
make prepush passes
ant tutorial runs

Change-Id: I5d070f347fd085cd247cb3d3e856ed83ec5311a7
Reviewed-on: https://gerrit.franz.com:9080/984
Reviewed-by: Kevin Layer <layer@franz.com>
Tested-by: Kevin Layer <layer@franz.com>
  • Loading branch information...
1 parent db12f7f commit b3f70d332618aac340ac34c0fa138c762d65950f Bill Millar committed with dklayer Nov 9, 2010
@@ -373,4 +373,21 @@ public void close() {
Util.close(this.mManager);
}
+ public String[] generateURIs(String repositoryURL, String namespace,
+ int amount) throws IOException, RepositoryException,
+ UnauthorizedException {
+ String url = repositoryURL + "/encodedIds";
+ Header[] headers = new Header[0];
+ NameValuePair[] data = { new NameValuePair("prefix", namespace),
+ new NameValuePair(AMOUNT_PARAM_NAME, Integer.toString(amount)) };
+ AGResponseHandler handler = new AGResponseHandler("");
+ try {
+ post(url, headers, data, null, handler);
+ } catch (RDFParseException e) {
+ throw new RepositoryException(e);
+ }
+ return handler.getString().split("\n");
+ }
+
+
}
@@ -1466,4 +1466,51 @@ public void setBulkMode(boolean bulkMode) throws RepositoryException {
public boolean isBulkMode() throws RepositoryException {
return bulkMode;
}
+
+ public void registerEncodableNamespace(String namespace, String format)
+ throws RepositoryException {
+ String url = getRoot() + "/encodedIds/prefixes";
+ Header[] headers = {};
+ List<NameValuePair> params = new ArrayList<NameValuePair>(2);
+ params.add(new NameValuePair("prefix", namespace));
+ params.add(new NameValuePair("format", format));
+ try {
+ getHTTPClient().post(url, headers,
+ params.toArray(new NameValuePair[params.size()]), null,
+ null);
+ } catch (HttpException e) {
+ throw new RepositoryException(e);
+ } catch (RDFParseException e) {
+ throw new RepositoryException(e);
+ } catch (IOException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void unregisterEncodableNamespace(String namespace)
+ throws RepositoryException {
+ String url = getRoot() + "/encodedIds/prefixes";
+ Header[] headers = {};
+ List<NameValuePair> params = new ArrayList<NameValuePair>(2);
+ params.add(new NameValuePair("prefix", namespace));
+ try {
+ getHTTPClient().delete(url, headers,
+ params.toArray(new NameValuePair[params.size()]));
+ } catch (HttpException e) {
+ throw new RepositoryException(e);
+ } catch (IOException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void registerEncodableNamespaces(JSONArray formattedNamespaces) throws RepositoryException {
+ String url = getRoot() + "/encodedIds/prefixes";
+ uploadJSON(url, formattedNamespaces);
+ }
+
+ public TupleQueryResult getEncodableNamespaces() throws RepositoryException {
+ String url = getRoot()+"/encodedIds/prefixes";
+ return getHTTPClient().getTupleQueryResult(url);
+ }
+
}
@@ -0,0 +1,22 @@
+package com.franz.agraph.repository;
+
+
+public class AGFormattedNamespace {
+
+ protected final String namespace;
+ protected final String format;
+
+ public AGFormattedNamespace(String namespace, String format) {
+ this.namespace = namespace;
+ this.format = format;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+}
@@ -20,6 +20,7 @@
import org.json.JSONArray;
import org.json.JSONException;
+import org.json.JSONObject;
import org.openrdf.OpenRDFException;
import org.openrdf.OpenRDFUtil;
import org.openrdf.model.Literal;
@@ -909,4 +910,94 @@ public void setBulkMode(boolean bulkMode) throws RepositoryException {
public boolean isBulkMode() throws RepositoryException {
return getHttpRepoClient().isBulkMode();
}
+
+ /**
+ * Registers an encodable namespace having the specified format.
+ *
+ * Registering an encodable namespace enables a more efficient
+ * encoding of URIs in a namespace, and generation of unique
+ * URIs for that namespace, because its URIs are declared to
+ * conform to a specified format; the namespace is thereby
+ * bounded in size, and encodable.
+ *
+ * The format is a string using a simplified regular expression
+ * syntax supporting character ranges and counts specifying the
+ * suffix portion of the URI's in the namespace.
+ *
+ * E.g., [a-z][0-9]-[a-f]{3}
+ *
+ * The format can be ambiguous (e.g., "[A-Z]{1,2}[B-C}{0,1}").
+ * We will not check for ambiguity in this first version but can
+ * add this checking at a later time.
+ *
+ * If the format corresponds to a namespace that is not encodable
+ * (it may be malformed, or perhaps it's too large to encode), an
+ * exception is thrown.
+ *
+ * @param namespace a valid namespace, a URI ref
+ * @param format a valid format for an encodable namespace
+ * @throws RepositoryException
+ */
+ public void registerEncodableNamespace(String namespace, String format) throws RepositoryException {
+ getHttpRepoClient().registerEncodableNamespace(namespace, format);
+ }
+
+
+ /**
+ * Registers multiple formatted namespaces in a single request.
+ *
+ * @param formattedNamespaces
+ * @throws RepositoryException
+ */
+ public void registerEncodableNamespaces(Iterable <? extends AGFormattedNamespace> formattedNamespaces) throws RepositoryException {
+ JSONArray rows = new JSONArray();
+ for (AGFormattedNamespace ns: formattedNamespaces) {
+ JSONObject row = new JSONObject();
+ try {
+ row.put("prefix",ns.getNamespace());
+ row.put("format", ns.getFormat());
+ } catch (JSONException e) {
+ throw new RepositoryException(e);
+ }
+ rows.put(row);
+ }
+ getHttpRepoClient().registerEncodableNamespaces(rows);
+ }
+
+ /**
+ * Returns a list of the registered encodable namespaces.
+ *
+ * @return a list of the registered encodable namespaces.
+ * @throws RepositoryException
+ */
+ public List<AGFormattedNamespace> listEncodableNamespaces()
+ throws OpenRDFException {
+ TupleQueryResult tqresult = getHttpRepoClient()
+ .getEncodableNamespaces();
+ List<AGFormattedNamespace> result = new ArrayList<AGFormattedNamespace>();
+ try {
+ while (tqresult.hasNext()) {
+ BindingSet bindingSet = tqresult.next();
+ Value prefix = bindingSet.getValue("prefix");
+ Value format = bindingSet.getValue("format");
+ result.add(new AGFormattedNamespace(prefix.stringValue(),
+ format.stringValue()));
+ }
+ } finally {
+ tqresult.close();
+ }
+ return result;
+ }
+
+
+ /**
+ * Unregisters the specified encodable namespace.
+ *
+ * @param namespace the namespace to unregister.
+ * @throws RepositoryException
+ */
+ public void unregisterEncodableNamespace(String namespace) throws RepositoryException {
+ getHttpRepoClient().unregisterEncodableNamespace(namespace);
+ }
+
}
@@ -17,6 +17,7 @@
import org.openrdf.model.Value;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.repository.RepositoryException;
+import org.openrdf.rio.ntriples.NTriplesUtil;
import com.franz.agraph.http.AGHTTPClient;
import com.hp.hpl.jena.graph.Node;
@@ -143,4 +144,63 @@ public URI asURI(Node node) {
}
return uri;
}
+
+
+ /***********************
+ *
+ * Encodable Namespaces
+ *
+ ***********************/
+
+ /**
+ * Returns unique URIs within the specified encodable namespace.
+ *
+ * The generated URIs will conform to the format that was specified
+ * when the encodable namespace was registered, and are guaranteed
+ * to be unique for this namespace generator. Note that this does
+ * not prevent other parties from independently using URIs that
+ * involve this namespace, however.
+ *
+ * If amount cannot be generated, up to amount URIs will be returned,
+ * or an exception will be thrown if none are available.
+ *
+ * @see AGRepositoryConnection#registerEncodableNamespace(String, String)
+ *
+ * @return a unique URI within the specified namespace.
+ * @throws RepositoryException
+ */
+ public URI[] generateURIs(String namespace, int amount) throws RepositoryException {
+ String[] uri_strs;
+ URI[] uris;
+ try {
+ uri_strs = getHTTPClient().generateURIs(getRepository().getRepositoryURL(),namespace,amount);
+ uris = new URI[uri_strs.length];
+ for (int i=0;i<uri_strs.length;i++) {
+ uris[i] = NTriplesUtil.parseURI(uri_strs[i],this);
+ }
+ } catch (UnauthorizedException e) {
+ throw new RepositoryException(e);
+ } catch (IOException e) {
+ throw new RepositoryException(e);
+ }
+ return uris;
+ }
+
+ /**
+ * Returns a unique URI within the specified encodable namespace.
+ *
+ * The generated URI will conform to the format that was specified
+ * when the encodable namespace was registered, and is guaranteed
+ * to be unique for this namespace generator. Note that this does
+ * not prevent other parties from independently using URIs that
+ * involve this namespace, however.
+ *
+ * @see AGRepositoryConnection#registerEncodableNamespace(String, String)
+ *
+ * @return a unique URI within the specified namespace.
+ * @throws RepositoryException
+ */
+ public URI generateURI(String registeredEncodableNamespace) throws RepositoryException {
+ return generateURIs(registeredEncodableNamespace,1)[0];
+ }
}
@@ -0,0 +1,115 @@
+/******************************************************************************
+** Copyright (c) 2008-2010 Franz Inc.
+** All rights reserved. This program and the accompanying materials
+** are made available under the terms of the Eclipse Public License v1.0
+** which accompanies this distribution, and is available at
+** http://www.eclipse.org/legal/epl-v10.html
+******************************************************************************/
+
+package test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.openrdf.model.URI;
+import org.openrdf.repository.RepositoryException;
+
+import com.franz.agraph.repository.AGFormattedNamespace;
+
+public class EncodableNamespaceTests extends AGAbstractTest {
+
+ @Test
+ @Category(TestSuites.Prepush.class)
+ public void encodableNamespaces_rfe10197() throws Exception {
+ String NS0 = "http://franz.com/ns0";
+ String FORMAT0 = "[a-z][0-9]-[a-f]{3}";
+ Assert.assertTrue("expected none", conn.listEncodableNamespaces().isEmpty());
+ conn.registerEncodableNamespace(NS0,FORMAT0);
+ List<AGFormattedNamespace> namespaces = conn.listEncodableNamespaces();
+ Assert.assertEquals("expected one", 1, namespaces.size());
+ AGFormattedNamespace ns = namespaces.get(0);
+ Assert.assertEquals("unexpected prefix", NS0, ns.getNamespace());
+ Assert.assertEquals("unexpected format", FORMAT0, ns.getFormat());
+ URI uri = vf.generateURI(NS0);
+ Assert.assertTrue("expected prefix "+NS0, uri.stringValue().startsWith(NS0));
+ Assert.assertFalse("expected uniqueness", uri.stringValue().equals(vf.generateURI(NS0).stringValue()));
+ String NS1 = "http://franz.com/ns1";
+ String NS2 = "http://franz.com/ns2";
+ String NS3 = "http://franz.com/ns3";
+ String NS4 = "urn:franz:";
+ conn.registerEncodableNamespace(NS1,"[0-1]{59}"); // 59's the max
+ conn.registerEncodableNamespace(NS2,"[0-9]{18}"); // 18's the max
+ try {
+ conn.registerEncodableNamespace(NS3,"[0-9]{19}"); // 19's too big
+ Assert.fail("expected exception for format too large.");
+ } catch (RepositoryException e) {
+ // expected
+ //System.out.println(e.getLocalizedMessage());
+ }
+ conn.registerEncodableNamespace(NS4,"[a-z][0-9]-[a-f]{1,3}");
+ try {
+ vf.generateURI(NS4);
+ Assert.fail("expected exception for format not fixed size.");
+ } catch (RepositoryException e) {
+ // expected
+ //System.out.println(e.getLocalizedMessage());
+ }
+ try {
+ uri = vf.generateURI(NS0);
+ conn.add(uri,uri,uri);
+ conn.registerEncodableNamespace(NS0,"[0-9]{10}");
+ Assert.fail("expected exception for attempting reregistration.");
+ } catch (RepositoryException e) {
+ // expected
+ //System.out.println(e.getLocalizedMessage());
+ }
+ namespaces = new ArrayList<AGFormattedNamespace>();
+ for (int i=0; i<10000; i++) {
+ namespaces.add(new AGFormattedNamespace("urn:franz-"+i+":","[0-1]{3}"));
+ }
+ conn.registerEncodableNamespaces(namespaces);
+ Assert.assertEquals(10004, conn.listEncodableNamespaces().size());
+ for (int i=0;i<8;i++) {
+ vf.generateURI("urn:franz-1234:");
+ }
+ try {
+ System.out.println(vf.generateURI("urn:franz-1234:"));
+ Assert.fail("expected exception for no more available id's.");
+ } catch (RepositoryException e) {
+ // expected
+ //System.out.println(e.getLocalizedMessage());
+ }
+ URI[] uris = vf.generateURIs("urn:franz-9999:", 5);
+ Assert.assertEquals("expected 5 URI's", 5, uris.length);
+ for (int i=0;i<uris.length;i++) {
+ //System.out.println(uris[i]);
+ }
+ uris = vf.generateURIs("urn:franz-9999:", 5);
+ Assert.assertEquals("expected 3 URI's", 3, uris.length);
+ for (int i=0;i<uris.length;i++) {
+ //System.out.println(uris[i]);
+ }
+ try {
+ uris = vf.generateURIs("urn:franz-9999:", 5);
+ Assert.fail("expected no URI's available");
+ } catch (RepositoryException e) {
+ // expected
+ //System.out.println(e.getLocalizedMessage());
+ }
+ conn.unregisterEncodableNamespace(NS2);
+ Assert.assertEquals(10003, conn.listEncodableNamespaces().size());
+ try {
+ conn.unregisterEncodableNamespace(NS0);
+ Assert.fail("expected namespace in use exception");
+ } catch (RepositoryException e) {
+ // expected
+ //System.out.println(e.getLocalizedMessage());
+ }
+ Assert.assertEquals(10003, conn.listEncodableNamespaces().size());
+ }
+
+}
@@ -54,7 +54,8 @@
RDFTransactionTest.class,
JenaTests.class,
BulkModeTests.class,
- IndexManagementTests.class
+ IndexManagementTests.class,
+ EncodableNamespaceTests.class
})
public static class Prepush {}

0 comments on commit b3f70d3

Please sign in to comment.