Author: aaime
Date: 2012-01-23 09:34:38 -0800 (Mon, 23 Jan 2012)
New Revision: 38519
Added:
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/ConnectionLifecycleListener.java
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnection.java
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnectionUnWrapper.java
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/SessionCommandsListener.java
branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCConnectionLifecycleTest.java
branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/SessionCommandListenerTest.java
branches/2.7.x/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ConnectionLifecycleTest.java
branches/2.7.x/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleConnectionLifecycleTest.java
branches/2.7.x/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisConnectionLifecycleTest.java
Modified:
branches/2.7.x/modules/library/api/src/main/java/org/geotools/data/Parameter.java
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStoreFactory.java
branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJNDIDataStoreFactory.java
branches/2.7.x/modules/library/jdbc/src/main/resources/META-INF/services/org.geotools.data.jdbc.datasource.UnWrapper
branches/2.7.x/modules/library/render/src/main/java/org/geotools/renderer/style/ExpressionExtractor.java
Log:
[GEOT-3994] Allow configurable database session startup and closeup sql commands
Modified: branches/2.7.x/modules/library/api/src/main/java/org/geotools/data/Parameter.java
===================================================================
--- branches/2.7.x/modules/library/api/src/main/java/org/geotools/data/Parameter.java 2012-01-23 08:23:14 UTC (rev 38518)
+++ branches/2.7.x/modules/library/api/src/main/java/org/geotools/data/Parameter.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -88,6 +88,11 @@
* provides a hint for UI's to mask text fields, configuration systems to encrypt content, etc
*/
public static final String IS_PASSWORD = "isPassword";
+
+ /** Boolean indicating whether the parameter is meant to be a long text,
+ * provides a hint for UI's use long text fields, textareas and the like
+ */
+ public static final String IS_LARGE_TEXT = "isLargeText";
/**
* "length" Integer used to limit the length of strings or literal geometries.
Added: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/ConnectionLifecycleListener.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/ConnectionLifecycleListener.java (rev 0)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/ConnectionLifecycleListener.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,52 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.jdbc;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * This interface can be implemented to perform custom behavior on each connection as it gets
+ * borrowed from the connection pool and then released back to the pool.
+ *
+ * @author Andrea Aime - GeoSolutions
+ *
+ */
+public interface ConnectionLifecycleListener {
+
+ /**
+ * Called when the collection is being borrowed from the connection pool
+ */
+ public void onBorrow(JDBCDataStore store, Connection cx) throws SQLException;
+
+ /**
+ * Called when the collection is being released back to the connection pool
+ */
+ public void onRelease(JDBCDataStore store, Connection cx) throws SQLException;
+
+ /**
+ * Called when the connection comes to a commit
+ * @throws SQLException
+ */
+ public void onCommit(JDBCDataStore store, Connection cx) throws SQLException;
+
+ /**
+ * Called when the connection comes to a rollback
+ */
+ public void onRollback(JDBCDataStore store, Connection cx) throws SQLException;
+
+}
Modified: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java 2012-01-23 08:23:14 UTC (rev 38518)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -42,6 +42,7 @@
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import javax.sql.DataSource;
@@ -263,6 +264,11 @@
* Contains the SQL definition of the various virtual tables
*/
protected Map<String, VirtualTable> virtualTables = new ConcurrentHashMap<String, VirtualTable>();
+
+ /**
+ * The listeners that are allowed to handle the connection lifecycle
+ */
+ protected List<ConnectionLifecycleListener> connectionLifecycleListeners = new CopyOnWriteArrayList<ConnectionLifecycleListener>();
/**
* Adds a virtual table to the data store. If a virtual table with the same name was registered this
@@ -283,6 +289,14 @@
}
/**
+ * Returns a modifiable list of connection lifecycle listeners
+ * @return
+ */
+ public List<ConnectionLifecycleListener> getConnectionLifecycleListeners() {
+ return connectionLifecycleListeners;
+ }
+
+ /**
* Removes and returns the specified virtual table
* @param name
* @return
@@ -1616,6 +1630,12 @@
//call dialect callback to initialize the connection
dialect.initializeConnection( cx );
+
+ // if there is any lifecycle listener use it
+ if(connectionLifecycleListeners.size() > 0) {
+ List<ConnectionLifecycleListener> locals = new ArrayList<ConnectionLifecycleListener>(connectionLifecycleListeners);
+ cx = new LifecycleConnection(this, cx, locals);
+ }
return cx;
} catch (SQLException e) {
throw new RuntimeException("Unable to obtain connection: " + e.getMessage(), e);
Modified: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStoreFactory.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStoreFactory.java 2012-01-23 08:23:14 UTC (rev 38518)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStoreFactory.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -113,6 +113,17 @@
public static final Param EXPOSE_PK = new Param("Expose primary keys", Boolean.class, "Expose primary key columns as " +
"attributes of the feature type", false, false);
+ /** SQL executed when the session begins */
+ public static final Param SQL_ON_BORROW = new Param("Session startup SQL", String.class,
+ "SQL statement executed when the connection is grabbed from the pool", false, null,
+ Collections.singletonMap(Parameter.IS_LARGE_TEXT, Boolean.TRUE));
+
+ /** SQL executed when the session ends */
+ public static final Param SQL_ON_RELEASE = new Param("Session close-up SQL", String.class,
+ "SQL statement executed when the connection is released to the pool", false, null,
+ Collections.singletonMap(Parameter.IS_LARGE_TEXT, Boolean.TRUE));
+
+
@Override
public String getDisplayName() {
return getDescription();
@@ -207,6 +218,14 @@
dataStore.setExposePrimaryKeyColumns(exposePk);
}
+ // session startup and teardown
+ String sqlOnBorrow = (String) SQL_ON_BORROW.lookUp(params);
+ String sqlOnRelease = (String) SQL_ON_RELEASE.lookUp(params);
+ if(sqlOnBorrow != null || sqlOnRelease != null) {
+ SessionCommandsListener listener = new SessionCommandsListener(sqlOnBorrow, sqlOnRelease);
+ dataStore.getConnectionLifecycleListeners().add(listener);
+ }
+
// factories
dataStore.setFilterFactory(CommonFactoryFinder.getFilterFactory(null));
dataStore.setGeometryFactory(new GeometryFactory());
@@ -292,6 +311,8 @@
if(getValidationQuery() != null)
parameters.put(VALIDATECONN.key, VALIDATECONN);
parameters.put(PK_METADATA_TABLE.key, PK_METADATA_TABLE);
+ parameters.put(SQL_ON_BORROW.key, SQL_ON_BORROW);
+ parameters.put(SQL_ON_RELEASE.key, SQL_ON_RELEASE);
}
/**
Modified: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJNDIDataStoreFactory.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJNDIDataStoreFactory.java 2012-01-23 08:23:14 UTC (rev 38518)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJNDIDataStoreFactory.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -157,7 +157,8 @@
parameters.put(NAMESPACE.key, NAMESPACE);
parameters.put(EXPOSE_PK.key, EXPOSE_PK);
parameters.put(PK_METADATA_TABLE.key, PK_METADATA_TABLE);
-
+ parameters.put(SQL_ON_BORROW.key, SQL_ON_BORROW);
+ parameters.put(SQL_ON_RELEASE.key, SQL_ON_RELEASE);
}
@Override
Added: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnection.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnection.java (rev 0)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnection.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,228 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.jdbc;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Calls the {@link ConnectionLifecycleListener#onBorrow(Connection)} method on construction and
+ * makes sure the {@link ConnectionLifecycleListener#onRelease(Connection)} is called when this
+ * connection is closed (the assumption is that the wrapped connection is pooled, as such on "close"
+ * it will actually be returned to the pool)
+ *
+ * @author Andrea Aime - GeoSolutions
+ */
+class LifecycleConnection implements Connection {
+
+ Connection delegate;
+
+ JDBCDataStore store;
+
+ List<ConnectionLifecycleListener> listeners;
+
+ public LifecycleConnection(JDBCDataStore store, Connection delegate, List<ConnectionLifecycleListener> listeners)
+ throws SQLException {
+ this.delegate = delegate;
+ this.listeners = listeners;
+ this.store = store;
+
+ for (ConnectionLifecycleListener listener : listeners) {
+ listener.onBorrow(store, delegate);
+ }
+ }
+
+ public void commit() throws SQLException {
+ for (ConnectionLifecycleListener listener : listeners) {
+ listener.onCommit(store, delegate);
+ }
+ delegate.commit();
+ }
+
+ public void rollback() throws SQLException {
+ delegate.rollback();
+ for (ConnectionLifecycleListener listener : listeners) {
+ listener.onRollback(store, delegate);
+ }
+ }
+
+ public void close() throws SQLException {
+ try {
+ for (ConnectionLifecycleListener listener : listeners) {
+ listener.onRelease(store, delegate);
+ }
+ } finally {
+ delegate.close();
+ }
+ }
+
+ public Statement createStatement() throws SQLException {
+ return delegate.createStatement();
+ }
+
+ public PreparedStatement prepareStatement(String sql) throws SQLException {
+ return delegate.prepareStatement(sql);
+ }
+
+ public CallableStatement prepareCall(String sql) throws SQLException {
+ return delegate.prepareCall(sql);
+ }
+
+ public String nativeSQL(String sql) throws SQLException {
+ return delegate.nativeSQL(sql);
+ }
+
+ public void setAutoCommit(boolean autoCommit) throws SQLException {
+ delegate.setAutoCommit(autoCommit);
+ }
+
+ public boolean getAutoCommit() throws SQLException {
+ return delegate.getAutoCommit();
+ }
+
+ public boolean isClosed() throws SQLException {
+ return delegate.isClosed();
+ }
+
+ public DatabaseMetaData getMetaData() throws SQLException {
+ return delegate.getMetaData();
+ }
+
+ public void setReadOnly(boolean readOnly) throws SQLException {
+ delegate.setReadOnly(readOnly);
+ }
+
+ public boolean isReadOnly() throws SQLException {
+ return delegate.isReadOnly();
+ }
+
+ public void setCatalog(String catalog) throws SQLException {
+ delegate.setCatalog(catalog);
+ }
+
+ public String getCatalog() throws SQLException {
+ return delegate.getCatalog();
+ }
+
+ public void setTransactionIsolation(int level) throws SQLException {
+ delegate.setTransactionIsolation(level);
+ }
+
+ public int getTransactionIsolation() throws SQLException {
+ return delegate.getTransactionIsolation();
+ }
+
+ public SQLWarning getWarnings() throws SQLException {
+ return delegate.getWarnings();
+ }
+
+ public void clearWarnings() throws SQLException {
+ delegate.clearWarnings();
+ }
+
+ public Statement createStatement(int resultSetType, int resultSetConcurrency)
+ throws SQLException {
+ return delegate.createStatement(resultSetType, resultSetConcurrency);
+ }
+
+ public PreparedStatement prepareStatement(String sql, int resultSetType,
+ int resultSetConcurrency) throws SQLException {
+ return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency);
+ }
+
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
+ throws SQLException {
+ return delegate.prepareCall(sql, resultSetType, resultSetConcurrency);
+ }
+
+ public Map<String, Class<?>> getTypeMap() throws SQLException {
+ return delegate.getTypeMap();
+ }
+
+ public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+ delegate.setTypeMap(map);
+ }
+
+ public void setHoldability(int holdability) throws SQLException {
+ delegate.setHoldability(holdability);
+ }
+
+ public int getHoldability() throws SQLException {
+ return delegate.getHoldability();
+ }
+
+ public Savepoint setSavepoint() throws SQLException {
+ return delegate.setSavepoint();
+ }
+
+ public Savepoint setSavepoint(String name) throws SQLException {
+ return delegate.setSavepoint(name);
+ }
+
+ public void rollback(Savepoint savepoint) throws SQLException {
+ delegate.rollback(savepoint);
+ }
+
+ public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+ delegate.releaseSavepoint(savepoint);
+ }
+
+ public Statement createStatement(int resultSetType, int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ return delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
+ public PreparedStatement prepareStatement(String sql, int resultSetType,
+ int resultSetConcurrency, int resultSetHoldability) throws SQLException {
+ return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency,
+ resultSetHoldability);
+ }
+
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ return delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
+ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
+ throws SQLException {
+ return delegate.prepareStatement(sql, autoGeneratedKeys);
+ }
+
+ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+ return delegate.prepareStatement(sql, columnIndexes);
+ }
+
+ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+ return delegate.prepareStatement(sql, columnNames);
+ }
+
+
+
+}
Added: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnectionUnWrapper.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnectionUnWrapper.java (rev 0)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/LifecycleConnectionUnWrapper.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,31 @@
+package org.geotools.jdbc;
+
+import java.sql.Connection;
+import java.sql.Statement;
+
+import org.geotools.data.jdbc.datasource.UnWrapper;
+
+/**
+ * Un-wraps the {@link LifecycleConnection} instances
+ *
+ * @author Andrea Aime - GeoSolutions
+ */
+public class LifecycleConnectionUnWrapper implements UnWrapper {
+
+ public boolean canUnwrap(Connection conn) {
+ return conn instanceof LifecycleConnection;
+ }
+
+ public boolean canUnwrap(Statement st) {
+ return false;
+ }
+
+ public Connection unwrap(Connection conn) {
+ return ((LifecycleConnection) conn).delegate;
+ }
+
+ public Statement unwrap(Statement statement) {
+ throw new UnsupportedOperationException("This un-wrapper cannot operate on statements");
+ }
+
+}
\ No newline at end of file
Added: branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/SessionCommandsListener.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/SessionCommandsListener.java (rev 0)
+++ branches/2.7.x/modules/library/jdbc/src/main/java/org/geotools/jdbc/SessionCommandsListener.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,223 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.jdbc;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.geotools.factory.CommonFactoryFinder;
+import org.geotools.filter.function.EnvFunction;
+import org.opengis.filter.FilterFactory2;
+import org.opengis.filter.expression.Expression;
+
+/**
+ * A {@link ConnectionLifecycleListener} that executes custom SQL commands on connection grab and
+ * release. The SQL commands can contain environment variable references, where the enviroment
+ * variable reference contains a name and an eventual default value.
+ *
+ * Parsing rules are:
+ * <ul>
+ * <li>whatever is between <code>${</code> and <code>}</code> is considered a enviroment variable
+ * reference in the form <code>${name,defaultvalue}, the default value being optional</li>
+ * <li><code>$</code> and <code>}</code> can be used stand alone only escaped with <code>\</code>
+ * (e.g. <code>\$</code> and <code>\}</code>)</li>
+ * <li><code>\</code> can be used stand alone only escaped with another <code>\</code></li> (e.g.
+ * <code>\\</code>)
+ * <li>a enviroment variable name cannot contain a comma, which is used as the separator between the
+ * enviroemnt variable name and its default value (first comma acts as a separator)</li>
+ * <li>the default value is always interpreted as a string and expanded as such in the sql commands</li>
+ * </ul>
+ *
+ * Examples of valid expressions:
+ * <ul>
+ * <li>"one two three \} \$ \\" (simple literal with special chars escaped)</li>
+ * <li>"My name is ${name}" (a simple enviroment variable reference without a default value)</li>
+ * <li>"My name is ${name,Joe}" (a simple enviroment variable reference with a default value)</li>
+ * </ul>
+ *
+ * Examples of non valid expressions:
+ * <ul>
+ * <li>"bla ${myAttName" (unclosed expression section)</li>
+ * <li>"bla } bla" (<code>}</code> is reserved, should have been escaped)</li>
+ *
+ *
+ * @author Andrea Aime - GeoSolutions
+ */
+public class SessionCommandsListener implements ConnectionLifecycleListener {
+
+ FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
+
+ private Expression sqlOnBorrow;
+
+ private Expression sqlOnRelease;
+
+ public SessionCommandsListener(String sqlOnBorrow, String sqlOnRelease) {
+ this.sqlOnBorrow = expandEviromentVariables(sqlOnBorrow);
+ this.sqlOnRelease = expandEviromentVariables(sqlOnRelease);
+ }
+
+ public void onBorrow(JDBCDataStore store, Connection cx) throws SQLException {
+ if (sqlOnBorrow != null && !"".equals(sqlOnBorrow)) {
+ String command = sqlOnBorrow.evaluate(null, String.class);
+ Statement st = null;
+ try {
+ st = cx.createStatement();
+ st.execute(command);
+ } finally {
+ store.closeSafe(st);
+ }
+
+ }
+ }
+
+ public void onRelease(JDBCDataStore store, Connection cx) throws SQLException {
+ if (sqlOnRelease != null) {
+ String command = sqlOnRelease.evaluate(null, String.class);
+ Statement st = null;
+ try {
+ st = cx.createStatement();
+ st.execute(command);
+ } finally {
+ store.closeSafe(st);
+ }
+
+ }
+ }
+
+ public void onCommit(JDBCDataStore store, Connection cx) {
+ // nothing to do
+ }
+
+ public void onRollback(JDBCDataStore store, Connection cx) {
+ // nothing to do
+ }
+
+ /**
+ * Parses the original sql command and returns a Expression that has all environment variable
+ * references expanded to a {@link EnvFunction} call.</p>
+ * This code is partially copied from gt-renderer ExpressionExtractor code, but simplified
+ * to only have enviroment variable references instead of CQL to avoid creating a dependendcy
+ * cascading issue (ExpressionExtractor would have to be moved to gt-cql and gt-jdbc made
+ * to depend on it.
+ *
+ * @param sql
+ * @return
+ */
+ Expression expandEviromentVariables(String sql) {
+ if(sql == null || "".equals(sql)) {
+ return null;
+ }
+
+ boolean inEnvVariable = false;
+ List<Expression> expressions = new ArrayList<Expression>();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < sql.length(); i++) {
+ final char curr = sql.charAt(i);
+ final boolean last = (i == sql.length() - 1);
+ final char next = last ? 0 : sql.charAt(i + 1);
+
+ if (curr == '\\') {
+ if (last)
+ throw new IllegalArgumentException("Unescaped \\ at position " + (i + 1));
+
+ if (next == '\\')
+ sb.append('\\');
+ else if (next == '$')
+ sb.append('$');
+ else if (next == '}')
+ sb.append('}');
+ else
+ throw new IllegalArgumentException("Unescaped \\ at position " + (i + 1));
+
+ // skip the next character
+ i++;
+ } else if (curr == '$') {
+ if (last || next != '{')
+ throw new IllegalArgumentException("Unescaped $ at position " + (i + 1));
+ if (inEnvVariable)
+ throw new IllegalArgumentException(
+ "Already found a ${ sequence before the one at " + (i + 1));
+
+ // if we extracted a literal in between two expressions, add it to the result
+ if (sb.length() > 0) {
+ expressions.add(ff.literal(sb.toString()));
+ sb.setLength(0);
+ }
+
+ // mark the beginning and skip the next character
+ inEnvVariable = true;
+ i++;
+ } else if (curr == '}') {
+ if (!inEnvVariable)
+ throw new IllegalArgumentException(
+ "Already found a ${ sequence before the one at " + (i + 1));
+
+ if (sb.length() == 0)
+ throw new IllegalArgumentException(
+ "Invalid empty enviroment variable reference ${} at " + (i - 1));
+
+ String name = sb.toString();
+ String defaultValue = null;
+ int idx = name.indexOf(',');
+ if(idx >= 0) {
+ if(idx == 0) {
+ throw new IllegalArgumentException("There is no variable name before " +
+ "the comma, the valid format is '${name,defaultValue}'");
+ } else if(idx < name.length() - 1) {
+ defaultValue = name.substring(idx + 1);
+ name = name.substring(0, idx);
+ }
+ }
+ Expression env;
+ if(defaultValue != null) {
+ env = ff.function("env", ff.literal(name), ff.literal(defaultValue));
+ } else {
+ env = ff.function("env", ff.literal(name));
+ }
+ expressions.add(env);
+ sb.setLength(0);
+ inEnvVariable = false;
+ } else {
+ sb.append(curr);
+ }
+ }
+
+ // when done, if we are still in a environment variable reference it means it hasn't been
+ // closed
+ if (inEnvVariable) {
+ throw new IllegalArgumentException("Unclosed enviroment variable reference '" + sb
+ + "'");
+ } else if (sb.length() > 0) {
+ expressions.add(ff.literal(sb.toString()));
+ }
+
+ // now concatenate back all the references
+ if (expressions == null || expressions.size() == 0)
+ throw new IllegalArgumentException("The SQL command appears to be empty: " + sql);
+
+ Expression result = expressions.get(0);
+ for (int i = 1; i < expressions.size(); i++) {
+ result = ff.function("strConcat", result, expressions.get(i));
+ }
+
+ return result;
+ }
+
+}
Modified: branches/2.7.x/modules/library/jdbc/src/main/resources/META-INF/services/org.geotools.data.jdbc.datasource.UnWrapper
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/main/resources/META-INF/services/org.geotools.data.jdbc.datasource.UnWrapper 2012-01-23 08:23:14 UTC (rev 38518)
+++ branches/2.7.x/modules/library/jdbc/src/main/resources/META-INF/services/org.geotools.data.jdbc.datasource.UnWrapper 2012-01-23 17:34:38 UTC (rev 38519)
@@ -1 +1,2 @@
org.geotools.data.jdbc.datasource.DBCPUnWrapper
+org.geotools.jdbc.LifecycleConnectionUnWrapper
\ No newline at end of file
Added: branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCConnectionLifecycleTest.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCConnectionLifecycleTest.java (rev 0)
+++ branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCConnectionLifecycleTest.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,155 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.jdbc;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.geotools.data.DefaultTransaction;
+import org.geotools.data.Query;
+import org.geotools.data.Transaction;
+import org.geotools.data.simple.SimpleFeatureIterator;
+import org.geotools.feature.DefaultFeatureCollection;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+public abstract class JDBCConnectionLifecycleTest extends JDBCTestSupport {
+
+ protected MockListener mockListener = new MockListener();
+
+
+
+ JDBCFeatureStore featureStore;
+
+ protected void connect() throws Exception {
+ super.connect();
+ featureStore = (JDBCFeatureStore) dataStore.getFeatureSource(tname("ft1"));
+ }
+
+ /**
+ * Check null encoding is working properly
+ *
+ * @throws IOException
+ */
+ public void testListenerCalled() throws IOException {
+ dataStore.getConnectionLifecycleListeners().add(mockListener);
+
+ // read some features, this will force unwrapping in Oracle
+ SimpleFeatureIterator fi = null;
+ try {
+ fi = featureStore.getFeatures().features();
+ while(fi.hasNext()) {
+ fi.next();
+ }
+ } finally {
+ if(fi != null) {
+ fi.close();
+ }
+ }
+ assertTrue(mockListener.onBorrowCalled);
+ assertTrue(mockListener.onReleaseCalled);
+ assertFalse(mockListener.onCommitCalled);
+ assertFalse(mockListener.onRollbackCalled);
+
+ // now write something within a transaction
+ Transaction t = new DefaultTransaction();
+ SimpleFeatureBuilder b = new SimpleFeatureBuilder(featureStore.getSchema());
+ DefaultFeatureCollection collection = new DefaultFeatureCollection(null,
+ featureStore.getSchema());
+ featureStore.setTransaction(t);
+ for (int i = 3; i < 6; i++) {
+ b.set(aname("intProperty"), new Integer(i));
+ b.set(aname("geometry"), new GeometryFactory().createPoint(new Coordinate(i, i)));
+ collection.add(b.buildFeature(null));
+ }
+ featureStore.addFeatures(collection);
+ t.commit();
+ assertTrue(mockListener.onBorrowCalled);
+ assertTrue(mockListener.onReleaseCalled);
+ assertTrue(mockListener.onCommitCalled);
+ assertFalse(mockListener.onRollbackCalled);
+
+ // and now do a rollback
+ t.rollback();
+ assertTrue(mockListener.onRollbackCalled);
+ }
+
+ public void testConnectionReleased() throws IOException {
+ dataStore.getConnectionLifecycleListeners().add(new ExceptionListener());
+
+ // get a count repeatedly, if we fail to release the connections this will eventually lock up
+ for (int i = 0; i < 100; i++) {
+ // we don't actually expect an exception to percolate up since it's happening
+ // on the closeSafe method, that swallows exceptions
+ featureStore.getCount(Query.ALL);
+ }
+ }
+
+ private static class MockListener implements ConnectionLifecycleListener {
+
+ boolean onBorrowCalled = false;
+
+ boolean onReleaseCalled = false;
+
+ boolean onCommitCalled;
+
+ boolean onRollbackCalled;
+
+ public void onBorrow(JDBCDataStore store, Connection cx) throws SQLException {
+ onBorrowCalled = true;
+ }
+
+ public void onRelease(JDBCDataStore store, Connection cx) throws SQLException {
+ onReleaseCalled = true;
+ }
+
+ public void onCommit(JDBCDataStore store, Connection cx) throws SQLException {
+ onCommitCalled = true;
+
+ }
+
+ public void onRollback(JDBCDataStore store, Connection cx) throws SQLException {
+ onRollbackCalled = true;
+
+ }
+
+ }
+
+ private static class ExceptionListener implements ConnectionLifecycleListener {
+
+ public void onBorrow(JDBCDataStore store, Connection cx) throws SQLException {
+ // nothing to do
+ }
+
+ public void onRelease(JDBCDataStore store, Connection cx) throws SQLException {
+ throw new SQLException("Ha, are you relasing the connection anyways??");
+ }
+
+ public void onCommit(JDBCDataStore store, Connection cx) throws SQLException {
+ throw new SQLException("Nope, no writes sir");
+ }
+
+ public void onRollback(JDBCDataStore store, Connection cx) throws SQLException {
+ // nothing to do
+ }
+
+ }
+
+}
Added: branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/SessionCommandListenerTest.java
===================================================================
--- branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/SessionCommandListenerTest.java (rev 0)
+++ branches/2.7.x/modules/library/jdbc/src/test/java/org/geotools/jdbc/SessionCommandListenerTest.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,109 @@
+package org.geotools.jdbc;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.geotools.filter.function.EnvFunction;
+import org.junit.Test;
+
+import com.mockrunner.mock.jdbc.MockConnection;
+import com.mockrunner.mock.jdbc.MockStatement;
+
+public class SessionCommandListenerTest {
+
+ static class RecordingConnection extends MockConnection {
+
+ List<String> commands = new ArrayList<String>();
+
+ public java.sql.Statement createStatement() throws java.sql.SQLException {
+ return new MockStatement(this) {
+ public boolean execute(String sql) throws java.sql.SQLException {
+ commands.add(sql);
+ return false;
+ }
+ };
+ }
+ };
+
+ RecordingConnection conn = new RecordingConnection();
+
+ JDBCDataStore store = new JDBCDataStore();
+
+ @Test
+ public void testPlain() throws Exception {
+ SessionCommandsListener listener = new SessionCommandsListener("A", "B");
+
+ // check borrow
+ listener.onBorrow(store, conn);
+ assertEquals(1, conn.commands.size());
+ assertEquals("A", conn.commands.get(0));
+ conn.commands.clear();
+
+ // check release
+ listener.onRelease(store, conn);
+ assertEquals(1, conn.commands.size());
+ assertEquals("B", conn.commands.get(0));
+ }
+
+ @Test
+ public void testOnlyBorrow() throws Exception {
+ SessionCommandsListener listener = new SessionCommandsListener("A", null);
+
+ // check borrow
+ listener.onBorrow(store, conn);
+ assertEquals(1, conn.commands.size());
+ assertEquals("A", conn.commands.get(0));
+ conn.commands.clear();
+
+ // check release
+ listener.onRelease(store, conn);
+ assertEquals(0, conn.commands.size());
+ }
+
+ @Test
+ public void testOnlyRelease() throws Exception {
+ SessionCommandsListener listener = new SessionCommandsListener(null, "B");
+
+ // check borrow
+ listener.onBorrow(store, conn);
+ assertEquals(0, conn.commands.size());
+
+ // check release
+ listener.onRelease(store, conn);
+ assertEquals(1, conn.commands.size());
+ assertEquals("B", conn.commands.get(0));
+ }
+
+ @Test
+ public void testExpandVariables() throws Exception {
+ SessionCommandsListener listener = new SessionCommandsListener("call startSession('${user}')",
+ "call endSession('${user,joe}')");
+
+ // check borrow
+ EnvFunction.setLocalValue("user", "abcde");
+ listener.onBorrow(store, conn);
+ assertEquals(1, conn.commands.size());
+ assertEquals("call startSession('abcde')", conn.commands.get(0));
+ conn.commands.clear();
+
+ // check release
+ EnvFunction.clearLocalValues();
+ listener.onRelease(store, conn);
+ assertEquals(1, conn.commands.size());
+ assertEquals("call endSession('joe')", conn.commands.get(0));
+ conn.commands.clear();
+ }
+
+ @Test
+ public void testInvalid() throws Exception {
+ try {
+ new SessionCommandsListener("startSession('${user')", null);
+ fail("This should have failed, the syntax is not valid");
+ } catch(IllegalArgumentException e) {
+ // fine
+ }
+ }
+
+}
Modified: branches/2.7.x/modules/library/render/src/main/java/org/geotools/renderer/style/ExpressionExtractor.java
===================================================================
--- branches/2.7.x/modules/library/render/src/main/java/org/geotools/renderer/style/ExpressionExtractor.java 2012-01-23 08:23:14 UTC (rev 38518)
+++ branches/2.7.x/modules/library/render/src/main/java/org/geotools/renderer/style/ExpressionExtractor.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -152,6 +152,12 @@
return result;
}
+ /**
+ * Builds a CQL expression equivalent to the specified string, see class javadocs for
+ * rules on how to build the expression in string form
+ * @param expression
+ * @return
+ */
public static Expression extractCqlExpressions(String expression) {
return catenateExpressions(splitCqlExpressions(expression));
}
Added: branches/2.7.x/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ConnectionLifecycleTest.java
===================================================================
--- branches/2.7.x/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ConnectionLifecycleTest.java (rev 0)
+++ branches/2.7.x/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ConnectionLifecycleTest.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,84 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.data.h2;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.geotools.data.Query;
+import org.geotools.data.simple.SimpleFeatureSource;
+import org.geotools.jdbc.ConnectionLifecycleListener;
+import org.geotools.jdbc.JDBCConnectionLifecycleTest;
+import org.geotools.jdbc.JDBCDataStore;
+import org.geotools.jdbc.JDBCTestSetup;
+import org.geotools.jdbc.VirtualTable;
+
+
+public class H2ConnectionLifecycleTest extends JDBCConnectionLifecycleTest {
+
+ private class SetVariableListener implements ConnectionLifecycleListener {
+
+ double value;
+
+ public void onBorrow(JDBCDataStore store, Connection cx) throws SQLException {
+ Statement st = null;
+ try {
+ st = cx.createStatement();
+ st.execute("SET @MYVAR = " + value);
+ } finally {
+ store.closeSafe(st);
+ }
+ }
+
+ public void onRelease(JDBCDataStore store, Connection cx) throws SQLException {
+ // nothing to do
+ }
+
+ public void onCommit(JDBCDataStore store, Connection cx) throws SQLException {
+ // nothing to do
+ }
+
+ public void onRollback(JDBCDataStore store, Connection cx) throws SQLException {
+ // nothing to do
+ }
+
+ }
+
+ protected JDBCTestSetup createTestSetup() {
+ return new H2TestSetup();
+ }
+
+ public void testVariableListener() throws Exception {
+ // setup a virtual table using the user variable
+ VirtualTable vt = new VirtualTable("ft1var", "select * from \"geotools\".\"ft1\" where \"doubleProperty\" > @MYVAR");
+ dataStore.addVirtualTable(vt);
+
+ // setup a listener that uses said variable
+ SetVariableListener listener = new SetVariableListener();
+ dataStore.getConnectionLifecycleListeners().add(listener);
+
+ // set the value and test
+ listener.value = 1.0;
+ SimpleFeatureSource fs = dataStore.getFeatureSource("ft1var");
+ assertEquals(2, fs.getCount(Query.ALL));
+
+ listener.value = 10;
+ assertEquals(0, fs.getCount(Query.ALL));
+
+ }
+}
Added: branches/2.7.x/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleConnectionLifecycleTest.java
===================================================================
--- branches/2.7.x/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleConnectionLifecycleTest.java (rev 0)
+++ branches/2.7.x/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleConnectionLifecycleTest.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,29 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.data.oracle;
+
+import org.geotools.jdbc.JDBCConnectionLifecycleTest;
+import org.geotools.jdbc.JDBCTestSetup;
+
+public class OracleConnectionLifecycleTest extends JDBCConnectionLifecycleTest {
+
+ @Override
+ protected JDBCTestSetup createTestSetup() {
+ return new OracleTestSetup();
+ }
+
+}
Added: branches/2.7.x/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisConnectionLifecycleTest.java
===================================================================
--- branches/2.7.x/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisConnectionLifecycleTest.java (rev 0)
+++ branches/2.7.x/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisConnectionLifecycleTest.java 2012-01-23 17:34:38 UTC (rev 38519)
@@ -0,0 +1,28 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package org.geotools.data.postgis;
+
+import org.geotools.jdbc.JDBCConnectionLifecycleTest;
+import org.geotools.jdbc.JDBCTestSetup;
+
+
+public class PostgisConnectionLifecycleTest extends JDBCConnectionLifecycleTest {
+
+ protected JDBCTestSetup createTestSetup() {
+ return new PostGISTestSetup();
+ }
+}
|