/** * Copyright (c) 2002-2011 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.jdbc; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.neo4j.cypherdsl.CypherQuery; import org.neo4j.cypherdsl.expression.Expression; import org.neo4j.cypherdsl.grammar.Execute; import org.neo4j.cypherdsl.grammar.ExecuteWithParameters; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.jdbc.embedded.EmbeddedQueryExecutor; import org.neo4j.jdbc.rest.RestQueryExecutor; import java.sql.*; import java.util.*; import java.util.concurrent.Executor; /** * Implementation of Connection that delegates to the Neo4j REST API and sends queries as Cypher requests */ public class Neo4jConnection extends AbstractConnection { protected final static Log log = LogFactory.getLog(Neo4jConnection.class); private String url; private QueryExecutor queryExecutor; private boolean closed = false; private final Properties properties = new Properties(); private boolean debug; private Driver driver; private Version version; private SQLWarning sqlWarnings; private boolean readonly = false; public Neo4jConnection(Driver driver, String jdbcUrl, Properties properties) throws SQLException { this.driver = driver; this.url = jdbcUrl; this.properties.putAll(properties); this.debug = hasDebug(); final String connectionUrl = jdbcUrl.substring("jdbc:neo4j".length()); this.queryExecutor = createExecutor(connectionUrl,getUser(),getPassword()); this.version = this.queryExecutor.getVersion(); validateVersion(); } private QueryExecutor createExecutor(String connectionUrl, String user, String password) throws SQLException { if (connectionUrl.contains("://")) return new RestQueryExecutor(connectionUrl,user,password); return getDriver().createExecutor(connectionUrl,properties); } private String getPassword() { return properties.getProperty("password"); } private String getUser() { return properties.getProperty("user"); } private boolean hasAuth() { return properties.contains("user") && properties.contains("password"); } private boolean hasDebug() { return Connections.hasDebug(properties); } private void validateVersion() throws SQLException { if (version.getMajorVersion() != 1 || version.getMinorVersion() < 5) throw new SQLException("Unsupported Neo4j version:"+ version); } public Statement createStatement() throws SQLException { return debug(new Neo4jStatement(this)); } public PreparedStatement prepareStatement(String statement) throws SQLException { return debug(new Neo4jPreparedStatement(this, statement)); } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { } @Override public boolean getAutoCommit() throws SQLException { return true; } @Override public void commit() throws SQLException { } @Override public void rollback() throws SQLException { } public void close() throws SQLException { try { queryExecutor.stop(); } catch (Exception e) { e.printStackTrace(); } finally { closed = true; } } public boolean isClosed() throws SQLException { return closed; } public DatabaseMetaData getMetaData() throws SQLException { return debug(new Neo4jDatabaseMetaData(this)); } @Override public void setReadOnly(boolean readOnly) throws SQLException { this.readonly=readOnly; } @Override public boolean isReadOnly() throws SQLException { return readonly; } @Override public SQLWarning getWarnings() throws SQLException { return sqlWarnings; } @Override public void clearWarnings() throws SQLException { sqlWarnings = null; } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return debug(new Neo4jStatement(this)); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return debug(new Neo4jPreparedStatement(this, nativeSQL(sql))); } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return debug(new Neo4jStatement(this)); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return debug(new Neo4jPreparedStatement(this, nativeSQL(sql))); } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return debug(new Neo4jPreparedStatement(this, nativeSQL(sql))); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return debug(new Neo4jPreparedStatement(this, nativeSQL(sql))); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return debug(new Neo4jPreparedStatement(this, nativeSQL(sql))); } @Override public boolean isValid(int timeout) throws SQLException { return true; } // Connection helpers public ResultSet executeQuery(Execute execute) throws SQLException { if (execute instanceof ExecuteWithParameters) return executeQuery(execute.toString(), ((ExecuteWithParameters) execute).getParameters()); else return executeQuery(execute.toString(), Collections.<String, Object>emptyMap()); } public ResultSet executeQuery(final String query, Map<String, Object> parameters) throws SQLException { if (log.isInfoEnabled()) log.info("Executing query: "+query+"\n with params"+parameters); checkReadOnly(query); try { final ExecutionResult result = queryExecutor.executeQuery(query, parameters); return debug(toResultSet(result)); } catch (SQLException e) { throw e; } catch (Exception e) { throw new SQLException(e); } } private void checkReadOnly(String query) throws SQLException { if (readonly && isMutating(query)) throw new SQLException("Mutating Query in readonly mode: "+query); } private boolean isMutating(String query) { return query.matches("(?is).*\\b(create|relate|delete|set)\\b.*"); } public String tableColumns(String tableName, String columnPrefix) throws SQLException { ResultSet columns = executeQuery(driver.getQueries().getColumns(tableName)); StringBuilder columnsBuilder = new StringBuilder(); while (columns.next()) { if (columnsBuilder.length() > 0) columnsBuilder.append(','); columnsBuilder.append(columnPrefix).append(columns.getString("property.name")); } return columnsBuilder.toString(); } public Iterable<Expression> returnProperties(String tableName, String columnPrefix) throws SQLException { ResultSet columns = executeQuery(driver.getQueries().getColumns(tableName)); List<Expression> properties = new ArrayList<Expression>(); while (columns.next()) { properties.add(CypherQuery.identifier(columnPrefix).property(columns.getString("property.name"))); } return properties; } String getURL() { return url; } protected ResultSet toResultSet(ExecutionResult result) throws SQLException { return new IteratorResultSet(this,result.columns(), result.getResult()); } public <T> T debug(T obj) { return Connections.debug(obj,debug); } public Properties getProperties() { return properties; } public Driver getDriver() { return driver; } public Version getVersion() { return version; } }