Permalink
Browse files

rfe10189: Java Interface for Stored Procedures

Experimental java API for calling stored procs.
New classes and methods are marked deprecated in the javadocs
because they may change in a future release.

The java code provided with rfe10189 is included,
and JKF's encoding code.

<release-note>
rfe10189: Java Interface for Stored Procedures

The stored proc feature and API are experimental,
and subject to change in a future release.
For this reason, the new methods are marked as deprecated.

The AllegroGraph server defines a new API for defining Stored
Procedures and they are installed like Custom Services.

The primary API is:
AGRepositoryConnection.callStoredProc(functionName, moduleName, args)

A low-level API is also exposed:
AGHttpRepoClient.callStoredProcEncoded()
AGSerializer.serializeAndEncode()
AGDeserializer.decodeAndDeserialize()
</release-note>

tests added:       to test encoding and stored proc call
tests run:         prepush

Depends on commit for rfe10189 in agraph:
https://gerrit.franz.com:9080/982

Change-Id: I6b735d76342473c8dae8b49a43638a769a5a9664
Reviewed-on: https://gerrit.franz.com:9080/964
Reviewed-by: Kevin Layer <layer@franz.com>
Tested-by: Kevin Layer <layer@franz.com>
  • Loading branch information...
1 parent b3f70d3 commit 43c9843184d80e8f86bf1a2e5ccb0d3a69d760d5 Mike Hinchey committed with dklayer Nov 19, 2010
View
58 src/com/franz/agraph/http/AGDecoder.java
@@ -0,0 +1,58 @@
+/******************************************************************************
+ ** 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 com.franz.agraph.http;
+
+/**
+ *
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+public class AGDecoder {
+
+ static byte charToCode[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 0, 0, 0, 0,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
+ 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+ };
+
+ public static byte[] decode(String sval){
+ ByteArray retv = new ByteArray();
+ int state = 0;
+ byte rem = 0;
+
+ for (int i = 0; i < sval.length(); i++) {
+ char ch = sval.charAt(i);
+ byte val = charToCode[ch];
+
+ switch (state) {
+ case 0: rem = val; break;
+
+ case 1: retv.addbyte((byte) (rem | ((val & 0x3) << 6)));
+ rem = (byte) (val >> 2);
+ break;
+
+ case 2: retv.addbyte((byte) (rem | ((val & 0xf) << 4)));
+ rem = (byte) (val >> 4);
+ break;
+
+ case 3: retv.addbyte((byte) (rem | (val << 2)));
+
+ }
+
+ if (++state > 3) state = 0;
+ }
+ return retv.extract();
+ }
+
+}
View
130 src/com/franz/agraph/http/AGDeserializer.java
@@ -0,0 +1,130 @@
+/******************************************************************************
+ ** 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 com.franz.agraph.http;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+public class AGDeserializer {
+
+ /* data to process */
+ private byte[] data;
+
+ private int pos;
+
+ private int max;
+
+ public static Object decodeAndDeserialize(String data) {
+ AGDeserializer o = new AGDeserializer( AGDecoder.decode(data) );
+ return o.deserialize();
+ }
+
+ public AGDeserializer(byte[] givendata) {
+ data = givendata;
+ pos = 0;
+ max = givendata.length;
+ }
+
+ byte nextbyte() {
+ if (pos >= max) {
+ throw new RuntimeException("ran off the end");
+ }
+ pos++;
+ return data[pos - 1];
+ }
+
+ int posInteger() {
+ int result = 0;
+ int shift = 0;
+
+ while (true) {
+ int val = nextbyte();
+ int masked;
+
+ masked = val & 0x7f;
+ result = result + (masked << shift);
+ if ((val & 0x80) == 0)
+ break;
+ shift += 7;
+ }
+
+ return result;
+
+ }
+
+ public Object deserialize() {
+ byte val = nextbyte();
+ int length;
+
+ switch (val) {
+ case SerialConstants.SO_BYTEVECTOR: {
+ length = posInteger();
+ byte[] res = new byte[length];
+ for (int i = 0; i < length; i++) {
+ res[i] = nextbyte();
+ }
+ return res;
+ }
+
+ case SerialConstants.SO_VECTOR: {
+ length = posInteger();
+ Object[] res = new Object[length];
+ for (int i = 0; i < length; i++) {
+ res[i] = deserialize();
+ }
+ return res;
+ }
+
+ case SerialConstants.SO_LIST: {
+ length = posInteger();
+ List res = new ArrayList(length);
+ for (int i = 0; i < length; i++) {
+ res.add( deserialize() );
+ }
+ // TODO: extra null needed by lisp side, bug in lisp?
+ nextbyte();
+ return res;
+ }
+
+ case SerialConstants.SO_STRING: {
+ length = posInteger();
+
+ StringBuilder res = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ res.append((char) nextbyte());
+ }
+ return res.toString();
+ }
+
+ case SerialConstants.SO_POS_INTEGER: {
+ return posInteger();
+ }
+
+ case SerialConstants.SO_NEG_INTEGER: {
+ return - posInteger();
+ }
+
+ case SerialConstants.SO_NULL:
+ return null;
+
+ case SerialConstants.SO_END_OF_ITEMS:
+ return null;
+
+ default:
+ throw new RuntimeException("bad code found by deserializer: " + val);
+
+ }
+ }
+
+}
View
69 src/com/franz/agraph/http/AGEncoder.java
@@ -0,0 +1,69 @@
+/******************************************************************************
+ ** 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 com.franz.agraph.http;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+public class AGEncoder {
+
+ static char codeToChar[] =
+ {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
+ 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '*', '+'
+ };
+
+ public static String encode(byte[] arr) {
+ ArrayList<Character> resx = new ArrayList<Character>();
+
+ int state = 0;
+
+ int rem = 0;
+
+ for (byte b : arr) {
+ switch (state) {
+ case 0:
+ resx.add(codeToChar[b & 0x3f]);
+ rem = (b >> 6) & 0x3;
+ break;
+ case 1:
+ resx.add(codeToChar[((b & 0xf) << 2) | rem]);
+ rem = (b >> 4) & 0xf;
+ break;
+ case 2:
+ resx.add(codeToChar[((b & 0x3) << 4) | rem]);
+ resx.add(codeToChar[((b >> 2) & 0x3f)]);
+ }
+
+ state = (state + 1) % 3;
+
+ }
+
+ if (state != 0) {
+ resx.add(codeToChar[rem]);
+ }
+
+ // there must be an easier way to turn an ArrayList<Character> into a String.
+ char[] retstr = new char[resx.size()];
+ int index = 0;
+ for (Character c : resx) {
+ retstr[index++] = c;
+ }
+ return new String(retstr);
+
+ }
+
+}
View
97 src/com/franz/agraph/http/AGHttpRepoClient.java
@@ -60,6 +60,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.franz.agraph.repository.AGCustomStoredProcException;
import com.franz.agraph.repository.AGQuery;
import com.franz.util.Closeable;
@@ -755,10 +756,7 @@ public void close() throws RepositoryException {
* Creates a new freetext index with the given parameters.
*
* See documentation here:
- *
- * {@link http://www.franz.com/agraph/support/documentation/v4/http-protocol.html#put-freetext-index}
- *
- * @throws RepositoryException
+ * <a href="http://www.franz.com/agraph/support/documentation/v4/http-protocol.html#put-freetext-index">put-freetext-index</a>
*/
public void createFreetextIndex(String name, List<String> predicates, boolean indexLiterals, List<String> indexLiteralTypes, String indexResources, List<String> indexFields, int minimumWordSize, List<String> stopWords, List<String> wordFilters)
throws RepositoryException {
@@ -864,10 +862,6 @@ public void deleteFreetextIndex(String index) throws RepositoryException {
/**
* Gets the configuration of the given index.
- *
- * @param index
- * @return
- * @throws RepositoryException
*/
public JSONObject getFreetextIndexConfiguration(String index)
throws RepositoryException {
@@ -1395,7 +1389,7 @@ public void registerSNANeighborMatrix(String matrix, String generator, List<Stri
* false, return only the current actively managed index types.
*
* @param listValid true yields all valid types, false yields active types.
- * @return
+ * @return list of indices, never null
* @throws OpenRDFException
*/
public List<String> listIndices(boolean listValid) throws RepositoryException {
@@ -1419,7 +1413,7 @@ public void registerSNANeighborMatrix(String matrix, String generator, List<Stri
* Adds the given index to the list of actively managed indices.
* This will take affect on the next commit.
*
- * @param type a valid index type
+ * @param index a valid index type
* @throws RepositoryException
* @see #listIndices(boolean)
*/
@@ -1442,7 +1436,7 @@ public void addIndex(String index) throws RepositoryException {
* Drops the given index from the list of actively managed indices.
* This will take affect on the next commit.
*
- * @param type a valid index type
+ * @param index a valid index type
* @throws RepositoryException
* @see #listIndices(boolean)
*/
@@ -1486,7 +1480,7 @@ public void registerEncodableNamespace(String namespace, String format)
throw new RepositoryException(e);
}
}
-
+
public void unregisterEncodableNamespace(String namespace)
throws RepositoryException {
String url = getRoot() + "/encodedIds/prefixes";
@@ -1502,7 +1496,7 @@ public void unregisterEncodableNamespace(String namespace)
throw new RepositoryException(e);
}
}
-
+
public void registerEncodableNamespaces(JSONArray formattedNamespaces) throws RepositoryException {
String url = getRoot() + "/encodedIds/prefixes";
uploadJSON(url, formattedNamespaces);
@@ -1512,5 +1506,82 @@ public TupleQueryResult getEncodableNamespaces() throws RepositoryException {
String url = getRoot()+"/encodedIds/prefixes";
return getHTTPClient().getTupleQueryResult(url);
}
+
+ /**
+ * Invoke a stored procedure on the AllegroGraph server.
+ * The args must already be encoded, and the response is encoded.
+ *
+ * <p>Low-level access to the data sent to the server can be done with:
+ * <code><pre>
+ * {@link AGDeserializer#decodeAndDeserialize(String) AGDeserializer.decodeAndDeserialize}(
+ * {@link #callStoredProcEncoded(String, String, String) callStoredProcEncoded}(functionName, moduleName,
+ * {@link AGSerializer#serializeAndEncode(Object[]) AGSerializer.serializeAndEncode}(args)));
+ * </code></pre></p>
+ *
+ * <p>If an error occurs in the stored procedure then result will
+ * be a two element vector with the first element being the string "_fail_"
+ * and the second element being the error message (also a string).</p>
+ *
+ * <p>{@link #callStoredProc(String, String, Object...)}
+ * does this encoding, decoding, and throws exceptions for error result.
+ * </p>
+ *
+ * @param functionName stored proc lisp function, for example "addTwo"
+ * @param moduleName lisp FASL file name, for example "example.fasl"
+ * @param argsEncoded byte-encoded arguments to the stored proc
+ * @return byte-encoded response from stored proc
+ * @see #callStoredProc(String, String, Object...)
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+ public String callStoredProcEncoded(String functionName, String moduleName, String argsEncoded)
+ throws RepositoryException {
+ String url = AGProtocol.getStoredProcLocation(repoRoot)+"/"+functionName;
+ Header[] headers = { new Header("x-scripts", moduleName) };
+ NameValuePair[] params = { new NameValuePair("spargstr", argsEncoded) };
+ AGResponseHandler handler = new AGResponseHandler("");
+ try {
+ getHTTPClient().post(url, headers, params, null, handler);
+ return handler.getString();
+ } catch (HttpException e) {
+ throw new RepositoryException(e);
+ } catch (IOException e) {
+ throw new RepositoryException(e);
+ } catch (RDFParseException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ /**
+ * Invoke a stored procedure on the AllegroGraph server.
+ *
+ * <p>The input arguments and the return value can be:
+ * {@link String}, {@link Integer}, null, byte[],
+ * or Object[] or {@link List} of these (can be nested).</p>
+ *
+ * @param functionName stored proc lisp function, for example "addTwo"
+ * @param moduleName lisp FASL file name, for example "example.fasl"
+ * @param args arguments to the stored proc
+ * @return return value of stored proc
+ * @throws AGCustomStoredProcException for errors from stored proc.
+ *
+ * @see AGSerializer#serializeAndEncode(Object[])
+ * @see AGDeserializer#decodeAndDeserialize(String)
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+ public Object callStoredProc(String functionName, String moduleName, Object...args)
+ throws RepositoryException {
+ Object o = AGDeserializer.decodeAndDeserialize(
+ callStoredProcEncoded(functionName, moduleName,
+ AGSerializer.serializeAndEncode(args)));
+ if (o instanceof Object[]) {
+ Object[] a = (Object[]) o;
+ if (a.length == 2 && "_fail_".equals(a[0])) {
+ throw new AGCustomStoredProcException(a[1] == null ? null : a[1].toString());
+ }
+ }
+ return o;
+ }
}
View
9 src/com/franz/agraph/http/AGProtocol.java
@@ -112,6 +112,11 @@
public static final String AUTOCOMMIT = "autoCommit";
/**
+ * Relative location of the custom stored proc service.
+ */
+ public static final String CUSTOM = "custom";
+
+ /**
* Parameter name for the 'on' parameter of autoCommit.
*/
public static final String ON_PARAM_NAME = "on";
@@ -469,6 +474,10 @@ public static final String getAutoCommitLocation(String sessionRoot) {
return getSessionURL(sessionRoot) + "/" + AUTOCOMMIT;
}
+ public static final String getStoredProcLocation(String sessionRoot) {
+ return sessionRoot + "/" + CUSTOM;
+ }
+
public static String getStatementsDeleteLocation(String sessionRoot) {
return getStatementsLocation(sessionRoot) + "/" + DELETE;
}
View
123 src/com/franz/agraph/http/AGSerializer.java
@@ -0,0 +1,123 @@
+/******************************************************************************
+ ** 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 com.franz.agraph.http;
+
+import java.util.List;
+
+/**
+ * Given a array of arrays and strings convert to a byte array.
+ *
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+public class AGSerializer {
+
+ private ByteArray barr;
+
+ /**
+ * @param data Strings, Integers, null, byte[], Object[], and {@link List} of these.
+ * @return byte-encoded and serialized to a string
+ */
+ public static String serializeAndEncode(Object[] data) {
+ AGSerializer o = new AGSerializer();
+ o.serializex(data);
+ return AGEncoder.encode(o.finish());
+ }
+
+ public AGSerializer() {
+ // start the serialization process
+ barr = new ByteArray();
+ }
+
+ public byte[] finish() {
+ // when all objects are serialized, this returns the byte
+ // array with the serialized data
+
+ barr.addbyte(SerialConstants.SO_END_OF_ITEMS);
+
+ byte[] retv = barr.extract();
+ barr = null; // to be gc'ed
+
+ return retv;
+ }
+
+ public AGSerializer serializex(Object obj) {
+ if (obj instanceof String) {
+ String str = (String) obj;
+ barr.addbyte(SerialConstants.SO_STRING);
+ serializeInteger(str.length());
+ for (int i = 0; i < str.length(); i++) {
+
+ // deal with unicode cvt via utf-8 here
+ barr.addbyte((byte) (str.codePointAt(i) & 0xff));
+ }
+ } else if (obj instanceof Integer) {
+ int i = (Integer) obj;
+ if (i >= 0) {
+ barr.addbyte(SerialConstants.SO_POS_INTEGER);
+ serializeInteger(i);
+ } else {
+ barr.addbyte(SerialConstants.SO_NEG_INTEGER);
+ serializeInteger(-i);
+ }
+ } else if (obj instanceof Object[]) {
+ Object[] vec = (Object[]) obj;
+ barr.addbyte(SerialConstants.SO_VECTOR);
+ serializeInteger(vec.length);
+ for (int i = 0; i < vec.length; i++) {
+ serializex(vec[i]);
+ }
+ } else if (obj instanceof byte[]) {
+ byte[] vec = (byte[]) obj;
+ barr.addbyte(SerialConstants.SO_BYTEVECTOR);
+ serializeInteger(vec.length);
+ for (int i = 0; i < vec.length; i++) {
+ barr.addbyte(vec[i]);
+ }
+ } else if (obj instanceof List) {
+ List vec = (List) obj;
+ barr.addbyte(SerialConstants.SO_LIST);
+ serializeInteger(vec.size());
+ for (int i = 0; i < vec.size(); i++) {
+ serializex(vec.get(i));
+ }
+ // TODO: extra null needed by lisp side, bug in lisp?
+ barr.addbyte(SerialConstants.SO_NULL);
+ } else if (obj == null) {
+ barr.addbyte(SerialConstants.SO_NULL);
+ } else {
+ throw new RuntimeException("cannot serialize object " + obj);
+ }
+
+ return this;
+
+ }
+
+ void serializeInteger(int i) {
+ // i is non negative
+ while (true) {
+ byte lower = (byte) (i & 0x7f);
+ int rest = i >> 7;
+
+ if (rest != 0) {
+ lower |= 0x80;
+
+ }
+ barr.addbyte(lower);
+
+ if (rest == 0) {
+ break;
+ }
+ i = rest;
+ }
+
+ return;
+
+ }
+}
View
63 src/com/franz/agraph/http/ByteArray.java
@@ -0,0 +1,63 @@
+/******************************************************************************
+ ** 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 com.franz.agraph.http;
+
+/**
+ * useful operations on byte arrays
+ */
+class ByteArray {
+
+ private byte[] data;
+ private int wpos; // next index into which to write
+
+ public ByteArray() {
+ data = new byte[1024];
+ wpos = 0;
+ }
+
+ public ByteArray(byte[] barr) {
+ data = barr;
+ wpos = data.length;
+ }
+
+ public void append(byte[] src, int max) {
+ // add the given byte array up to max to the curret
+ // byte array
+ if (wpos + max >= data.length) {
+ // need new data
+ byte[] newdata = new byte[wpos + max + 2048];
+ System.arraycopy(data, 0, newdata, 0, wpos);
+ data = newdata;
+ }
+
+ System.arraycopy(src, 0, data, wpos, max);
+ wpos += max;
+ }
+
+ void addbyte(byte b) {
+ if (wpos + 1 >= data.length) {
+ // need new data
+ byte[] newdata = new byte[wpos + 2048];
+ System.arraycopy(data, 0, newdata, 0, wpos);
+ data = newdata;
+ }
+ data[wpos] = b;
+ wpos++;
+
+ }
+
+ public byte[] extract() {
+ // return a byte array with the contents of this object
+ byte[] retobj = new byte[wpos];
+
+ System.arraycopy(data, 0, retobj, 0, wpos);
+ return retobj;
+ }
+
+}
View
25 src/com/franz/agraph/http/SerialConstants.java
@@ -0,0 +1,25 @@
+/******************************************************************************
+** 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 com.franz.agraph.http;
+
+/**
+ *
+ */
+interface SerialConstants {
+
+ byte SO_VECTOR = 1;
+ byte SO_STRING = 5;
+ byte SO_NULL = 7;
+ byte SO_LIST = 8;
+ byte SO_POS_INTEGER = 9;
+ byte SO_END_OF_ITEMS = 10;
+ byte SO_NEG_INTEGER = 11;
+ byte SO_BYTEVECTOR = 15; // usb8 vector
+
+}
View
31 src/com/franz/agraph/repository/AGCustomStoredProcException.java
@@ -0,0 +1,31 @@
+/******************************************************************************
+ ** 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 com.franz.agraph.repository;
+
+import org.openrdf.repository.RepositoryException;
+
+/**
+ * Error message returned by custom stored procedure.
+ *
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+public class AGCustomStoredProcException extends RepositoryException {
+
+ private static final long serialVersionUID = -6193406940075840175L;
+
+ public AGCustomStoredProcException(String message) {
+ super(message);
+ }
+
+ public AGCustomStoredProcException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
View
28 src/com/franz/agraph/repository/AGRepositoryConnection.java
@@ -942,7 +942,6 @@ public void registerEncodableNamespace(String namespace, String format) throws R
getHttpRepoClient().registerEncodableNamespace(namespace, format);
}
-
/**
* Registers multiple formatted namespaces in a single request.
*
@@ -989,7 +988,6 @@ public void registerEncodableNamespaces(Iterable <? extends AGFormattedNamespace
return result;
}
-
/**
* Unregisters the specified encodable namespace.
*
@@ -1000,4 +998,30 @@ public void unregisterEncodableNamespace(String namespace) throws RepositoryExce
getHttpRepoClient().unregisterEncodableNamespace(namespace);
}
+ /**
+ * Invoke a stored procedure on the AllegroGraph server.
+ *
+ * <p>The input arguments and the return value can be:
+ * {@link String}, {@link Integer}, null, byte[],
+ * or Object[] or {@link List} of these (can be nested).</p>
+ *
+ * <p>See also
+ * {@link #getHttpRepoClient()}.{@link AGHttpRepoClient#callStoredProc(String, String, Object...)
+ * callStoredProc}<code>(functionName, moduleName, args)</code>
+ * </p>
+ *
+ * @param functionName stored proc lisp function, for example "addTwo"
+ * @param moduleName lisp FASL file name, for example "example.fasl"
+ * @param args arguments to the stored proc
+ * @return return value of stored proc
+ * @throws AGCustomStoredProcException for errors from stored proc
+ *
+ * @since v4.2
+ * @deprecated The stored proc feature and API are experimental, and subject to change in a future release.
+ */
+ public Object callStoredProc(String functionName, String moduleName, Object...args)
+ throws RepositoryException {
+ return getHttpRepoClient().callStoredProc(functionName, moduleName, args);
+ }
+
}
View
2 src/com/franz/util/Util.java
@@ -63,5 +63,5 @@ public static CloseableIteration close(CloseableIteration o) {
}
return null;
}
-
+
}
View
44 src/test/AGAbstractTest.java
@@ -25,6 +25,7 @@
import org.junit.After;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.openrdf.model.Statement;
@@ -168,6 +169,10 @@ AGRepositoryConnection getConnection(AGAbstractRepository repo) throws Repositor
public static void assertSetsEqual(Collection expected, Set actual) {
assertSetsEqual("", expected, actual);
}
+
+ public static void assertSetsEqual(String msg, byte[] expected, byte[] actual) {
+ assertSetsEqual(msg, Util.toList(expected), Util.toList(actual));
+ }
public static void assertSetsEqual(String msg, Collection expected, Collection actual) {
expected = new ArrayList(expected);
@@ -189,6 +194,45 @@ public static void assertSetsEqual(String msg, Collection expected, Collection a
assertEquals(msg + ". Remaining: " + actual, 0, actual.size());
}
+ public static void assertEqualsDeep(String msg, Object expected, Object actual) {
+ if (expected == null) {
+ Assert.assertEquals(msg, expected, actual);
+ } else if (actual == null) {
+ Assert.assertEquals(msg, expected, actual);
+ } else if (expected instanceof List) {
+ List expList = (List) expected;
+ Assert.assertTrue(msg + "; expected Collection type, actual: " + actual.getClass(), actual instanceof List);
+ List actList = (List) actual;
+ Assert.assertTrue(msg + "; expected same size=" + expList.size() + ", actual=" + actList.size(),
+ expList.size() == actList.size());
+ for (int i = 0; i < expList.size(); i++) {
+ assertEqualsDeep("[" + i +"]" + msg, expList.get(i), actList.get(i));
+ }
+ } else if (expected instanceof Object[]) {
+ Object[] expList = (Object[]) expected;
+ Assert.assertTrue(msg + "; expected Object[] type, actual: " + actual.getClass(), actual instanceof Object[]);
+ Object[] actList = (Object[]) actual;
+ Assert.assertTrue(msg + "; expected same size=" + expList.length + ", actual=" + actList.length,
+ expList.length == actList.length);
+ for (int i = 0; i < expList.length; i++) {
+ assertEqualsDeep("[" + i +"]" + msg, expList[i], actList[i]);
+ }
+ } else if (expected instanceof byte[]) {
+ byte[] expList = (byte[]) expected;
+ Assert.assertTrue(msg + "; expected byte[] type, actual: " + actual.getClass(), actual instanceof byte[]);
+ byte[] actList = (byte[]) actual;
+ Assert.assertTrue(msg + "; expected same size=" + expList.length + ", actual=" + actList.length,
+ expList.length == actList.length);
+ for (int i = 0; i < expList.length; i++) {
+ assertEqualsDeep("[" + i +"]" + msg, expList[i], actList[i]);
+ }
+ } else if (expected instanceof Set) {
+ assertSetsEqual(msg, (Set) expected, (Collection) actual);
+ } else {
+ assertEquals(msg, expected, actual);
+ }
+ }
+
public static void assertSetsSome(String msg, Collection expected, Collection actual) {
for (Iterator ait = actual.iterator(); ait.hasNext();) {
Object act = ait.next();
View
201 src/test/QuickTests.java
@@ -14,6 +14,8 @@
import static test.Stmt.statementSet;
import static test.Stmt.stmts;
+import java.io.IOException;
+import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
@@ -33,12 +35,24 @@
import org.openrdf.query.QueryLanguage;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.RDFHandlerException;
+import org.openrdf.rio.RDFParseException;
+import org.openrdf.rio.RDFParser;
+import org.openrdf.rio.Rio;
+import org.openrdf.rio.helpers.StatementCollector;
+import test.TestSuites.NonPrepushTest;
+
+import com.franz.agraph.http.AGDecoder;
+import com.franz.agraph.http.AGDeserializer;
+import com.franz.agraph.http.AGEncoder;
+import com.franz.agraph.http.AGSerializer;
+import com.franz.agraph.repository.AGCustomStoredProcException;
import com.franz.agraph.repository.AGRepository;
import com.franz.agraph.repository.AGRepositoryConnection;
-import test.TestSuites.NonPrepushTest;
-
+@SuppressWarnings("deprecation")
public class QuickTests extends AGAbstractTest {
@RunWith(Categories.class)
@@ -177,4 +191,185 @@ public void openRepo_rfe9837() throws Exception {
}
}
}
-}
+
+ @Test
+ @Category(TestSuites.Prepush.class)
+ public void storedProcs_encoding_rfe10189() throws Exception {
+ byte[][] cases = {{ 1, 3, 32, 11, 13, 123},
+ {},
+ {33},
+ {33, 44},
+ {33, 44, 55},
+ {33, 44, 55, 66},
+ {33, 44, 55, 66, 77},
+ {33, 44, 55, 66, 77, 88},
+ {-1, -2, -3, -4, -5},
+ {-1, -2, -3, -4}
+ };
+
+ for (int casenum = 0 ; casenum < cases.length; casenum++){
+ byte[] input = cases[casenum];
+ String encoded = AGEncoder.encode(input);
+ byte[] result = AGDecoder.decode(encoded);
+ assertSetsEqual("encoding", input, result);
+ }
+ }
+
+ /**
+ * Example class of how a user might wrap a stored-proc for convenience.
+ */
+ class SProcTest {
+ static final String FASL = "ag-test-stored-proc.fasl";
+
+ private final AGRepositoryConnection conn;
+ SProcTest(AGRepositoryConnection conn) {
+ this.conn = conn;
+ }
+
+ String addTwoStrings(String a, String b) throws Exception {
+ return (String) conn.callStoredProc("add-two-strings", FASL, a, b);
+ }
+
+ int addTwoInts(int a, int b) throws Exception {
+ return (Integer) conn.callStoredProc("add-two-ints", FASL, a, b);
+ }
+
+ String addTwoVecStrings(String a, String b) throws Exception {
+ return (String) conn.callStoredProc("add-two-vec-strings", FASL, a, b);
+ }
+
+ String addTwoVecStringsError() throws Exception {
+ return (String) conn.callStoredProc("add-two-vec-strings", FASL);
+ }
+
+ int addTwoVecInts(int a, int b) throws Exception {
+ return (Integer) conn.callStoredProc("add-two-vec-ints", FASL, a, b);
+ }
+
+ Object bestBeNull(String a) throws Exception {
+ return conn.callStoredProc("best-be-nil", FASL, a);
+ }
+
+ Object returnAllTypes() throws Exception {
+ return conn.callStoredProc("return-all-types", FASL);
+ }
+
+ Object identity(Object input) throws Exception {
+ return conn.callStoredProc("identity", FASL, input);
+ }
+
+ Object checkAllTypes(Object input) throws Exception {
+ return conn.callStoredProc("check-all-types", FASL, input);
+ }
+
+ Object addATripleInt(int i) throws Exception {
+ return conn.callStoredProc("add-a-triple-int", FASL, i);
+ }
+
+ Statement getATripleInt(int i) throws Exception {
+ String r = (String) conn.callStoredProc("get-a-triple-int", FASL, i);
+ Statement st = parseNtriples(r);
+ return st;
+ }
+
+ private Statement parseNtriples(String ntriples) throws IOException,
+ RDFParseException, RDFHandlerException {
+ RDFParser parser = Rio.createParser(RDFFormat.NTRIPLES, vf);
+ parser.setPreserveBNodeIDs(true);
+ StatementCollector collector = new StatementCollector();
+ parser.setRDFHandler(collector);
+ parser.parse(new StringReader(ntriples), "http://example.com/");
+ Statement st = collector.getStatements().iterator().next();
+ return st;
+ }
+
+// Object addATriple(Object s, Object p, Object o) throws Exception {
+// return conn.callStoredProc("add-a-triple", FASL, s, p, o);
+// }
+
+ }
+
+ static final Object ALL_TYPES = new Object[] {
+ 123,
+ 0,
+ -123,
+ "abc",
+ null,
+ new Integer[] {9, 9, 9, 9},
+ Util.arrayList(123,0, -123, "abc"),
+ new byte[] {0, 1, 2, 3, 4, 5, 6, 7}
+ };
+
+ @Test
+ @Category(TestSuites.Prepush.class)
+ public void encoding_all_types_rfe10189() throws Exception {
+ Object[] o = new Object[] {ALL_TYPES};
+ assertEqualsDeep("all types", o,
+ AGDeserializer.decodeAndDeserialize(AGSerializer.serializeAndEncode(o)));
+ }
+
+ @Test
+ @Category(TestSuites.Prepush.class)
+ public void storedProcsEncoded_rfe10189() throws Exception {
+ String response = (String) AGDeserializer.decodeAndDeserialize(
+ conn.getHttpRepoClient().callStoredProcEncoded("add-two-strings", SProcTest.FASL,
+ AGSerializer.serializeAndEncode(
+ new String[] {"123", "456"})));
+ assertEquals(579, Integer.parseInt(response));
+ }
+
+ @Test
+ @Category(TestSuites.Prepush.class)
+ public void storedProcs_rfe10189() throws Exception {
+ SProcTest sp = new SProcTest(conn);
+ assertEquals("supports strings", "579", sp.addTwoStrings("123", "456"));
+ assertEquals("supports pos int", 579, sp.addTwoInts(123, 456));
+ assertEquals("supports neg int and zero", 0, sp.addTwoInts(123, -123));
+ assertEquals("supports neg int", -100, sp.addTwoInts(23, -123));
+ assertEquals("supports whole arg-vec strings", "579", sp.addTwoVecStrings("123", "456"));
+ assertEquals("supports whole arg-vec ints", 579, sp.addTwoVecInts(123, 456));
+ assertEquals("supports null", null, sp.bestBeNull(null));
+ try {
+ assertEquals(null, sp.bestBeNull("abc"));
+ fail("should be AGCustomStoredProcException");
+ } catch (AGCustomStoredProcException e) {
+ assertEquals("test null and error", "I expected a nil, but got: abc", e.getMessage());
+ }
+ try {
+ assertEquals("579", sp.addTwoVecStringsError());
+ fail("should be AGCustomStoredProcException");
+ } catch (AGCustomStoredProcException e) {
+ assertEquals("test error", "wrong number of args", e.getMessage());
+ }
+ try {
+ assertEquals("579", sp.addTwoVecStrings(null, null));
+ fail("should be AGCustomStoredProcException");
+ } catch (AGCustomStoredProcException e) {
+ assertEquals("test null and error", "There is no integer in the string nil (:start 0 :end 0)", e.getMessage());
+ }
+ try {
+ assertEquals("579", sp.addTwoVecStrings("abc", "def"));
+ fail("should be AGCustomStoredProcException");
+ } catch (AGCustomStoredProcException e) {
+ assertEquals("test error", "There's junk in this string: \"abc\".", e.getMessage());
+ }
+ System.out.println(ALL_TYPES);
+ assertEqualsDeep("supports all types, originating from java", ALL_TYPES, sp.checkAllTypes(ALL_TYPES));
+ assertEqualsDeep("supports all types, round-trip", ALL_TYPES, sp.identity(ALL_TYPES));
+ assertEqualsDeep("supports all types, originating from lisp", ALL_TYPES, sp.returnAllTypes());
+ }
+
+ @Test
+ @Category(TestSuites.Prepush.class)
+ public void storedProcs_triples_rfe10189() throws Exception {
+ SProcTest sp = new SProcTest(conn);
+ Assert.assertNotNull("add-a-triple-int", sp.addATripleInt(1));
+ assertEqualsDeep("get-a-triple-int", Stmt.stmts(new Stmt(vf.createURI("http://test.com/add-a-triple-int"),
+ vf.createURI("http://test.com/p"), vf.createLiteral(1))),
+ Stmt.stmts( new Stmt(sp.getATripleInt(1))));
+ // TODO: transferring triples does not work yet
+ // TODO: change the expected return value when that is known
+ //Assert.assertNotNull("add-a-triple", sp.addATriple(vf.createURI("http://test.com/s"), vf.createURI("http://test.com/p"), vf.createURI("http://test.com/p")));
+ }
+
+}
View
80 src/test/Util.java
@@ -16,6 +16,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.zip.GZIPOutputStream;
@@ -113,4 +114,83 @@ public static void gzip(File in, File out) throws IOException {
}
}
+ public static List arrayList(Object...elements) {
+ List list = new ArrayList();
+ for (int i = 0; i < elements.length; i++) {
+ list.add(elements[i]);
+ }
+ return list;
+ }
+
+ /**
+ * List Arrays.asList, but is not varargs,
+ * also allows null (returns null), and will
+ * convert primitive arrays to List of wrapper objects.
+ * @return list or null
+ */
+ public static List toList(Object arr) {
+ if (arr == null) {
+ return null;
+ }
+ if (arr instanceof List) {
+ return (List) arr;
+ }
+ if (arr instanceof Object[]) {
+ return Arrays.asList((Object[])arr);
+ }
+ List list = new ArrayList();
+ if (arr instanceof byte[]) {
+ byte[] a = ((byte[])arr);
+ for (int i = 0; i < a.length; i++) {
+ list.add(a[i]);
+ }
+ } else if (arr instanceof char[]) {
+ char[] a = ((char[])arr);
+ for (int i = 0; i < a.length; i++) {
+ list.add(a[i]);
+ }
+ } else if (arr instanceof int[]) {
+ int[] a = ((int[])arr);
+ for (int i = 0; i < a.length; i++) {
+ list.add(a[i]);
+ }
+ } else if (arr instanceof long[]) {
+ long[] a = ((long[])arr);
+ for (int i = 0; i < a.length; i++) {
+ list.add(a[i]);
+ }
+ } else if (arr instanceof float[]) {
+ float[] a = ((float[])arr);
+ for (int i = 0; i < a.length; i++) {
+ list.add(a[i]);
+ }
+ } else if (arr instanceof double[]) {
+ double[] a = ((double[])arr);
+ for (int i = 0; i < a.length; i++) {
+ list.add(a[i]);
+ }
+ } else {
+ throw new IllegalArgumentException("type not handled: " + arr.getClass());
+ }
+ return list;
+ }
+
+ public static List toListDeep(Object obj) {
+ List in = toList(obj);
+ if (in == null) {
+ return null;
+ }
+ List out = new ArrayList(in.size());
+ for (Object o : in) {
+ if (o == null) {
+ out.add(null);
+ } else if (o instanceof List || o.getClass().isArray()) {
+ out.add(toListDeep(o));
+ } else {
+ out.add(o);
+ }
+ }
+ return out;
+ }
+
}
View
2 src/test/stress/Events.java
@@ -48,14 +48,12 @@
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.query.BindingSet;
-import org.openrdf.query.GraphQueryResult;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.ntriples.NTriplesUtil;
import com.franz.agraph.repository.AGCatalog;
-import com.franz.agraph.repository.AGGraphQuery;
import com.franz.agraph.repository.AGQueryLanguage;
import com.franz.agraph.repository.AGRepository;
import com.franz.agraph.repository.AGRepositoryConnection;

0 comments on commit 43c9843

Please sign in to comment.