package org.neo4j.jdbc.rest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import org.neo4j.jdbc.*;
import org.restlet.Client;
import org.restlet.Response;
import org.restlet.data.CharacterSet;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.sql.SQLNonTransientConnectionException;
import java.util.Map;
/**
* @author mh
* @since 15.06.12
*/
public class RestQueryExecutor implements QueryExecutor {
protected final static Log log = LogFactory.getLog(RestQueryExecutor.class);
private final static Client client = new Client("HTTP");
private String url;
private ClientResource cypherResource;
private ObjectMapper mapper = new ObjectMapper();
private Version version;
public RestQueryExecutor(String connectionUrl, String user, String password) throws SQLException {
try
{
url = "http" + connectionUrl;
if (log.isInfoEnabled())log.info("Connecting to URL "+url);
Resources resources = new Resources(url, client);
if (user!=null && password!=null) {
resources.setAuth(user, password);
}
Resources.DiscoveryClientResource discovery = resources.getDiscoveryResource();
version = new Version(discovery.getVersion());
String cypherPath = discovery.getCypherPath();
cypherResource = resources.getCypherResource(cypherPath);
} catch (IOException e)
{
throw new SQLNonTransientConnectionException(e);
}
}
public ExecutionResult executeQuery(String query, Map<String, Object> parameters) throws SQLException {
final ClientResource resource = new ClientResource(cypherResource);
try {
ObjectNode queryNode = queryParameter(query, parameters);
Representation rep = resource.post(queryNode.toString());
rep.setCharacterSet(new CharacterSet("UTF-8"));
JsonNode node = mapper.readTree(rep.getReader());
final ResultParser parser = new ResultParser(node);
return new ExecutionResult(parser.getColumns(), parser.streamData());
} catch (ResourceException e) {
String msg=extractErrorMessage(resource);
if (msg!=null) throw new SQLException(msg,e);
throw new SQLException(e.getStatus().getReasonPhrase(), e);
} catch (JsonProcessingException e) {
throw new SQLException(e);
} catch (IOException e) {
throw new SQLException(e);
}
}
/**
* When a REST error occurs, the JSON can contain an error message
*/
private String extractErrorMessage(ClientResource resource) {
try {
Response resp = resource.getResponse();
if (resp == null) return null;
Representation rep = resp.getEntity();
rep.setCharacterSet(new CharacterSet("UTF-8"));
JsonNode node = mapper.readTree(rep.getReader());
if (node == null) return null;
JsonNode msg = node.findValue("message");
if (msg == null) return null;
return msg.getTextValue();
} catch (Exception ex) {
return null;
}
}
@Override
public void stop() throws Exception {
((Client) cypherResource.getNext()).stop();
}
@Override
public Version getVersion() {
return version;
}
private ObjectNode queryParameter(String query, Map<String, Object> parameters) {
ObjectNode queryNode = mapper.createObjectNode();
queryNode.put("query", escapeQuery(query));
queryNode.put("params", parametersNode(parameters));
return queryNode;
}
private String escapeQuery(String query) {
query = query.replace('\"', '\'');
query = query.replace('\n', ' ');
return query;
}
private ObjectNode parametersNode(Map<String, Object> parameters) {
ObjectNode params = mapper.createObjectNode();
for (Map.Entry<String, Object> entry : parameters.entrySet())
{
final String name = entry.getKey();
final Object value = entry.getValue();
if (value==null) {
params.putNull(name);
} else if (value instanceof String)
params.put(name, value.toString());
else if (value instanceof Integer)
params.put(name, (Integer) value);
else if (value instanceof Long)
params.put(name, (Long) value);
else if (value instanceof Boolean)
params.put(name, (Boolean) value);
else if (value instanceof BigDecimal)
params.put(name, (BigDecimal) value);
else if (value instanceof Double)
params.put(name, (Double) value);
else if (value instanceof byte[])
params.put(name, (byte[]) value);
else if (value instanceof Float)
params.put(name, (Float) value);
else if (value instanceof Number) {
final Number number = (Number) value;
if (number.longValue()==number.doubleValue()) {
params.put(name, number.longValue());
} else {
params.put(name, number.doubleValue());
}
}
}
return params;
}
}