/*
* 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.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.factory.Hints;
import org.geotools.util.logging.Logging;
import com.vividsolutions.jts.geom.Geometry;
/**
* Describes a virtual table, that is, a feature type created starting from a generic SQL query.
* This class also carries information about the primary key (to generate stable feature ids) and
* the geometry type and native srid (as in most databases those informations are not available on.
*
* The sql query can contain named parameters. Each parameter has a name, a default value and a way
* to validate its contents to prevent sql injection
*
* @author Andrea Aime - OpenGeo
*
*
* @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/build/maven/javadoc/../../../modules/library/jdbc/src/main/java/org/geotools/jdbc/VirtualTable.java $
*/
public class VirtualTable implements Serializable {
static final Logger LOGGER = Logging.getLogger(VirtualTable.class);
String name;
String sql;
List<String> primaryKeyColumns = new CopyOnWriteArrayList<String>();
Map<String, Class<? extends Geometry>> geometryTypes = new ConcurrentHashMap<String, Class<? extends Geometry>>();
Map<String, Integer> nativeSrids = new ConcurrentHashMap<String, Integer>();
Map<String, VirtualTableParameter> parameters = new ConcurrentHashMap<String, VirtualTableParameter>();
/**
* Builds a new virtual table stating its name and the query to be executed to work on it
*
* @param name
* @param sql
*/
public VirtualTable(String name, String sql) {
this.name = name;
this.sql = sql;
}
/**
* Clone a virtual table under a different name
* @param name
* @param other
*/
public VirtualTable(String name, VirtualTable other) {
this.name = name;
this.sql = other.sql;
this.geometryTypes = new ConcurrentHashMap<String, Class<? extends Geometry>>(other.geometryTypes);
this.nativeSrids = new ConcurrentHashMap<String, Integer>(other.nativeSrids);
this.parameters = new ConcurrentHashMap<String, VirtualTableParameter>(other.parameters);
this.primaryKeyColumns = new ArrayList<String>(other.primaryKeyColumns);
}
/**
* Clone a virtual table
* @param name
* @param other
*/
public VirtualTable(VirtualTable other) {
this.name = other.name;
this.sql = other.sql;
this.geometryTypes = new ConcurrentHashMap<String, Class<? extends Geometry>>(other.geometryTypes);
this.nativeSrids = new ConcurrentHashMap<String, Integer>(other.nativeSrids);
this.parameters = new ConcurrentHashMap<String, VirtualTableParameter>(other.parameters);
this.primaryKeyColumns = new ArrayList<String>(other.primaryKeyColumns);
}
/**
* Returns the virtual table primary key columns. It should refer to fields returned by the
* query, if that is not true the behavior is undefined
*/
public List<String> getPrimaryKeyColumns() {
return primaryKeyColumns;
}
/**
* Sets the virtual table primary key
*
* @param primaryKeyColumns
*/
public void setPrimaryKeyColumns(List<String> primaryKeyColumns) {
this.primaryKeyColumns.clear();
if (primaryKeyColumns != null) {
this.primaryKeyColumns.addAll(primaryKeyColumns);
}
}
/**
* The virtual table name
*
* @return
*/
public String getName() {
return name;
}
/**
* The virtual table sql (raw, without parameter expansion)
*
* @return
*/
public String getSql() {
return sql;
}
public String expandParameters(Hints hints) throws SQLException {
// no need for expansion if we don't have parameters
if (parameters.size() == 0) {
return sql;
}
// grab the parameter values
Map<String, String> values = null;
if(hints != null) {
values = (Map<String, String>) hints.get(Hints.VIRTUAL_TABLE_PARAMETERS);
}
if (values == null) {
values = Collections.emptyMap();
}
// perform the expansion, checking for validity and applying default values as needed
String result = sql;
for (VirtualTableParameter param : parameters.values()) {
String value = values.get(param.getName());
if(value == null) {
// use the default value and eventually prepare to expand the empty string
value = param.getDefaultValue();
if(value == null) {
value = "";
}
} else {
if(param.getValidator() != null) {
try {
param.getValidator().validate(value);
} catch(IllegalArgumentException e) {
// fully log the exception, but only rethrow a more generic description as
// the message could be exposed to attackers
LOGGER.log(Level.SEVERE, "Invalid value for parameter " + param.getName(), e);
throw new SQLException("Invalid value for parameter " + param.getName());
}
}
}
result = result.replace("%" + param.getName() + "%", value);
}
return result;
}
/**
* Adds geometry metadata to the virtual table. This is important to get the datastore working,
* often that is not the case if the right native srid is not in place
*
* @param geometry
* @param binding
* @param nativeSrid
*/
public void addGeometryMetadatata(String geometry, Class<? extends Geometry> binding,
int nativeSrid) {
geometryTypes.put(geometry, binding);
nativeSrids.put(geometry, nativeSrid);
}
/**
* Adds a parameter to the virtual table
* @param param
*/
public void addParameter(VirtualTableParameter param) {
parameters.put(param.getName(), param);
}
/**
* Removes a parameter from the virtual table
* @param paramName
*/
public void removeParameter(String paramName) {
parameters.remove(paramName);
}
/**
* The current parameter names
* @return
*/
public Collection<String> getParameterNames() {
return new ArrayList(parameters.keySet());
}
/**
* Returns the requested parameter, or null if it could not be found
* @return
*/
public VirtualTableParameter getParameter(String name) {
return parameters.get(name);
}
/**
* Returns the geometry's specific type, or null if not known
*
* @param geometryName
* @return
*/
public Class<? extends Geometry> getGeometryType(String geometryName) {
return geometryTypes.get(geometryName);
}
/**
* Returns the name of the geometry colums declared in this virtual table
*
* @return
*/
public Set<String> getGeometries() {
return geometryTypes.keySet();
}
/**
* Returns the geometry native srid, or -1 if not known
*
* @param geometryName
* @return
*/
public int getNativeSrid(String geometryName) {
Integer srid = nativeSrids.get(geometryName);
if (srid == null) {
srid = -1;
}
return srid;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((geometryTypes == null) ? 0 : geometryTypes.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((nativeSrids == null) ? 0 : nativeSrids.hashCode());
result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
result = prime * result + ((primaryKeyColumns == null) ? 0 : primaryKeyColumns.hashCode());
result = prime * result + ((sql == null) ? 0 : sql.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VirtualTable other = (VirtualTable) obj;
if (geometryTypes == null) {
if (other.geometryTypes != null)
return false;
} else if (!geometryTypes.equals(other.geometryTypes))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (nativeSrids == null) {
if (other.nativeSrids != null)
return false;
} else if (!nativeSrids.equals(other.nativeSrids))
return false;
if (parameters == null) {
if (other.parameters != null)
return false;
} else if (!parameters.equals(other.parameters))
return false;
if (primaryKeyColumns == null) {
if (other.primaryKeyColumns != null)
return false;
} else if (!primaryKeyColumns.equals(other.primaryKeyColumns))
return false;
if (sql == null) {
if (other.sql != null)
return false;
} else if (!sql.equals(other.sql))
return false;
return true;
}
@Override
public String toString() {
return "VirtualTable [name=" + name + ", sql=" + sql + "]";
}
}