package org.reldb.rel.client;
import java.io.*;
import java.net.MalformedURLException;
import java.util.Stack;
import java.util.Vector;
import org.reldb.rel.client.Error;
import org.reldb.rel.client.connection.CrashHandler;
import org.reldb.rel.client.connection.CrashHandlerDefault;
import org.reldb.rel.client.connection.stream.ClientFromURL;
import org.reldb.rel.client.connection.stream.ClientLocalConnection;
import org.reldb.rel.client.connection.stream.InputStreamInterceptor;
import org.reldb.rel.client.connection.stream.StreamReceiverClient;
import org.reldb.rel.client.parser.ResponseAdapter;
import org.reldb.rel.client.parser.ResponseToHTML;
import org.reldb.rel.client.parser.ResponseToHTMLProgressive;
import org.reldb.rel.client.parser.core.ParseException;
import org.reldb.rel.client.parser.core.ResponseParser;
import org.reldb.rel.exceptions.DatabaseFormatVersionException;
/**
* Connection to a Rel database.
*
* @author dave
*/
public class Connection {
private String dbURL;
private String serverAnnouncement = "";
private CrashHandler crashHandler;
private String[] additionalJars;
private final static String errorPrefix = "ERROR:";
private static class Snippet {
private StringBuffer buffer = new StringBuffer();
public void clear() {
buffer = null;
}
public void create() {
buffer = new StringBuffer();
}
public boolean isClear() {
return (buffer == null);
}
public void append(int n) {
buffer.append((char)n);
}
public String toString() {
if (buffer == null)
return "";
return buffer.toString();
}
}
/** Creates new connection, with additional JAR support for database development. */
public Connection(String dbURL, boolean createDbAllowed, CrashHandler crashHandler, String[] additionalJars) throws NumberFormatException, MalformedURLException, IOException, DatabaseFormatVersionException {
this.dbURL = dbURL;
this.crashHandler = crashHandler;
this.additionalJars = additionalJars;
// Make sure it exists.
ClientFromURL.openConnection(dbURL, createDbAllowed, crashHandler, additionalJars).close();
}
/** Creates new connection. */
public Connection(String dbURL, boolean createDbAllowed, CrashHandler crashHandler) throws NumberFormatException, MalformedURLException, IOException, DatabaseFormatVersionException {
this(dbURL, createDbAllowed, crashHandler, new String[0]);
}
/** Creates new connection using CrashHandlerDefault. */
public Connection(String dbURL, boolean createDbAllowed) throws NumberFormatException, MalformedURLException, IOException, DatabaseFormatVersionException {
this(dbURL, createDbAllowed, new CrashHandlerDefault());
}
/** Creates new connection. Error thrown if database doesn't exist. */
public Connection(String dbURL) throws NumberFormatException, MalformedURLException, IOException, DatabaseFormatVersionException {
this(dbURL, false);
}
/** Attempts update of a database.
* @throws IOException
* @throws DatabaseFormatVersionException */
public static void convertToLatestFormat(String dbURL, PrintStream conversionOutput, String[] additionalJars) throws DatabaseFormatVersionException, IOException {
ClientLocalConnection.convertToLatestFormat(dbURL, conversionOutput, additionalJars);
}
public String getDbURL() {
return dbURL;
}
public CrashHandler getCrashHandler() {
return crashHandler;
}
public String[] getAdditionalJars() {
return additionalJars;
}
private abstract class Action {
public abstract void run(StreamReceiverClient client) throws IOException;
};
public interface CharacterListener {
public void receive(int character);
}
private Vector<CharacterListener> characterListeners = new Vector<CharacterListener>();
/** Add listener which receives every character in the response. */
public void addCharacterListener(CharacterListener listener) {
characterListeners.addElement(listener);
}
/** Remove listener which receives every character in the response. */
public void removeCharacterListener(CharacterListener listener) {
characterListeners.removeElement(listener);
}
/** Override to obtain every character received in the response. If super.capturedResponseStream(character) is not
* called in an overridden capturedResponseStream, addCharacterListener becomes a no-op. */
protected void capturedResponseStream(int character) {
for (CharacterListener listener: characterListeners)
listener.receive(character);
}
private class ErrorMessageTrap extends InputStreamInterceptor {
private Snippet errorMessageTrap = new Snippet();
ErrorMessageTrap(InputStream input) {
super(input);
}
public void interceptedRead(int r) {
capturedResponseStream(r);
if (errorMessageTrap.isClear()) {
if (r == '\n')
errorMessageTrap.create();
} else {
errorMessageTrap.append(r);
String possibleErrorStr = errorMessageTrap.toString();
if (possibleErrorStr.length() >= errorPrefix.length() && !possibleErrorStr.startsWith(errorPrefix))
errorMessageTrap.clear();
}
}
public String toString() {
return errorMessageTrap.toString();
}
}
private void launchTransmitter(final StreamReceiverClient client, final Action action) {
// Transmit needs to run in separate thread for local connection, or the pipe will deadlock.
Thread sendRunner = new Thread() {
public void run() {
try {
action.run(client);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
};
sendRunner.start();
}
private Response launchParser(final Action sendAction, final Action receiveComplete) {
final Response response = new Response();
final StreamReceiverClient client;
try {
client = ClientFromURL.openConnection(dbURL, false, crashHandler, additionalJars);
} catch (Exception e) {
response.setResult(new Error(e.toString()));
return response;
}
Thread parseRunner = new Thread() {
public void run() {
ErrorMessageTrap errorMessageTrap;
ResponseParser parser;
try {
errorMessageTrap = new ErrorMessageTrap(client.getServerResponseInputStream());
parser = new ResponseParser(errorMessageTrap);
} catch (IOException e1) {
e1.printStackTrace();
return;
}
parser.setResponseHandler(new ResponseAdapter() {
Stack<Value> valueReceiver = new Stack<Value>();
Stack<Heading> headingReceiver = new Stack<Heading>();
private void endData() {
Value value = valueReceiver.pop();
if (valueReceiver.size() > 0)
valueReceiver.peek().addValue(value, false);
else
response.setResult(value);
}
public void beginHeading(String typeName) {
Heading heading = new Heading(typeName);
headingReceiver.push(heading);
}
public Heading endHeading() {
Heading heading = headingReceiver.pop();
if (headingReceiver.size() > 0)
headingReceiver.peek().addAttributeType(heading);
return heading;
}
public void attributeName(String name) {
headingReceiver.peek().addAttributeName(name);
}
public void typeReference(String name) {
headingReceiver.peek().addAttributeType(new ScalarType(name));
}
public void beginScalar(int depth) {
valueReceiver.push(new Scalar());
}
public void endScalar(int depth) {
endData();
}
public void beginPossrep(String name) {
valueReceiver.push(new Selector(name));
}
public void endPossrep() {
endData();
}
public void primitive(String value, boolean quoted) {
valueReceiver.peek().addValue(new Scalar(value, quoted), quoted);
}
public void beginContainerBody(int depth, Heading heading, String typeName) {
Tuples tuples = (heading == null) ? new Tuples(typeName) : new Tuples(heading);
if (depth == 0)
response.setResult(tuples);
valueReceiver.push(tuples);
}
public void endContainer(int depth) {
Tuples tuples = (Tuples)valueReceiver.peek();
tuples.insertNullTuple();
endData();
}
public void beginTuple(int depth) {
valueReceiver.push(new Tuple());
}
public void endTuple(int depth) {
endData();
}
public void attributeNameInTuple(int depth, String name) {
((Tuple)valueReceiver.peek()).addAttributeName(name);
}
});
try {
parser.parse();
} catch (ParseException e) {
response.setResult(new Error(errorMessageTrap.toString()));
}
try {
if (receiveComplete != null)
receiveComplete.run(client);
client.close();
} catch (IOException e) {
System.out.println("Connection: run failed: " + e);
e.printStackTrace();
}
}
};
parseRunner.start();
launchTransmitter(client, sendAction);
return response;
}
public interface HTMLReceiver {
public void emitInitialHTML(String s);
public void endInitialHTML();
public void emitProgressiveHTML(String s);
public void endProgressiveHTMLRow();
}
private void launchParserToHTML(final Action action, final HTMLReceiver htmlReceiver) {
final StreamReceiverClient client;
try {
client = ClientFromURL.openConnection(dbURL, false, crashHandler, additionalJars);
} catch (Exception e) {
htmlReceiver.emitInitialHTML("Unable to open connection: " + e.toString().replace(" ", " "));
return;
}
Thread parseRunner = new Thread() {
public void run() {
ErrorMessageTrap errorMessageTrap;
ResponseToHTML parser;
try {
errorMessageTrap = new ErrorMessageTrap(client.getServerResponseInputStream());
parser = new ResponseToHTMLProgressive(errorMessageTrap) {
public void emitInitialHTML(String s) {
htmlReceiver.emitInitialHTML(s);
}
public void endInitialHTML() {
htmlReceiver.endInitialHTML();
}
public void emitProgressiveHTML(String s) {
htmlReceiver.emitProgressiveHTML(s);
}
public void endProgressiveHTMLRow() {
htmlReceiver.endProgressiveHTMLRow();
}
};
} catch (IOException e1) {
e1.printStackTrace();
return;
}
try {
parser.parse();
} catch (ParseException e) {
htmlReceiver.emitInitialHTML(errorMessageTrap.toString().replace(" ", " "));
}
try {
client.close();
} catch (IOException e) {
System.out.println("Connection: close failed: " + e);
e.printStackTrace();
}
}
};
parseRunner.start();
launchTransmitter(client, action);
}
private static class Indicator {
boolean indicated = false;
public void setIndicated(boolean indicated) {this.indicated = indicated;}
boolean isIndicated() {return this.indicated;}
}
/** Execute query and return Response. */
public Response execute(final String input) throws IOException {
final Indicator finished = new Indicator();
Response response = launchParser(
new Action() {
public void run(StreamReceiverClient client) throws IOException {
client.sendExecute(input);
}
},
new Action() {
public void run(StreamReceiverClient client) throws IOException {
synchronized (finished) {
finished.setIndicated(true);
finished.notify();
}
}
}
);
synchronized (finished) {
while (!finished.isIndicated())
try {
finished.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
/** Get server announcement. */
public String getServerAnnouncement() {
return serverAnnouncement;
}
/** Evaluate query and return Response. */
public Response evaluate(final String input) throws IOException {
return launchParser(new Action() {
public void run(StreamReceiverClient client) throws IOException {
client.sendEvaluate(input);
}
}, null);
}
/** Evaluate query and emit response as HTML. */
public void evaluate(final String input, final HTMLReceiver htmlReceiver) {
launchParserToHTML(new Action() {
public void run(StreamReceiverClient client) throws IOException {
client.sendEvaluate(input);
}
}, htmlReceiver);
}
public static class ExecuteResult {
private Response response;
public ExecuteResult(Response response) {
this.response = response;
}
public boolean failed() {
return (response == null || response.getResult() instanceof Error);
}
public String getErrorMessage() {
if (response == null)
return "Connection failed.";
if (response.getResult() instanceof Error) {
String error = ((Error)response.getResult()).getErrorMsg();
int EOTposition = error.indexOf("<EOT>");
if (EOTposition >= 0)
error = error.substring(0, EOTposition);
return error;
}
return "Unknown error.";
}
}
/** Execute query. */
public ExecuteResult exec(String query) {
try {
return new ExecuteResult(execute(query));
} catch (IOException e1) {
return new ExecuteResult(null);
}
}
/** Evaluate query. */
public Value eval(String query, int queryWaitMilliseconds) {
Value response;
try {
response = evaluate(query).awaitResult(queryWaitMilliseconds);
} catch (IOException e) {
System.out.println("Connection: Error: " + e);
e.printStackTrace();
return null;
}
if (response instanceof Error) {
Error error = (Error)response;
System.out.println("Connection: Query evaluate returns error. " + query + "\n" + error.getErrorMsg());
return null;
}
if (response == null) {
System.out.println("Connection: Unable to obtain query results.");
return null;
}
return response;
}
/** Evaluate query that returns tuples. */
public Tuples getTuples(String query, int queryWaitMilliseconds) {
return (Tuples)eval(query, queryWaitMilliseconds);
}
}