/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-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; import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.geotools.factory.Hints; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * The query object is used by the {@link FeatureSource#getFeatures()} method of * the DataSource interface, to encapsulate a request. It defines which * feature type to query, what properties to retrieve and what constraints * (spatial and non-spatial) to apply to those properties. It is designed to * closesly match a WFS Query element of a <code>getFeatures</code> request. * The only difference is the addition of the maxFeatures element, which * allows more control over the features selected. It allows a full * <code>getFeatures</code> request to properly control how many features it * gets from each query, instead of requesting and discarding when the max is * reached. * * @author Chris Holmes * @source $URL$ */ public class DefaultQuery implements Query { /** The properties to fetch */ private String[] properties; /** The maximum numbers of features to fetch */ private int maxFeatures = Query.DEFAULT_MAX; private Integer startIndex = null; /** The filter to constrain the request. */ private Filter filter = Filter.INCLUDE; /** The typeName to get */ private String typeName; /** The namespace to get */ private URI namespace =Query.NO_NAMESPACE; /** The handle associated with this query. */ private String handle; /** Coordinate System associated with this query */ private CoordinateReferenceSystem coordinateSystem; /** Reprojection associated associated with this query */ private CoordinateReferenceSystem coordinateSystemReproject; /** Sorting for the query */ private SortBy[] sortBy; /** The version according to WFS 1.0 and 1.1 specs */ private String version; /** The hints to be used during query execution */ private Hints hints; /** /** * No argument constructor. */ public DefaultQuery() { // no arg } /** * Query with typeName. * <p> * </p> * @param typeName the name of the featureType to retrieve */ public DefaultQuery( String typeName ){ this( typeName, Filter.INCLUDE ); } /** * Constructor with typeName and filter. Note that current datasource * implementations only have one type per datasource, so the typeName * field will likely be ignored. * * @param typeName the name of the featureType to retrieve. * @param filter the OGC filter to constrain the request. */ public DefaultQuery(String typeName, Filter filter) { this( typeName, filter, Query.ALL_NAMES ); } /** * Constructor that sets the filter and properties * @param typeName * * @param filter the OGC filter to constrain the request. * @param properties an array of the properties to fetch. */ public DefaultQuery(String typeName, Filter filter, String[] properties) { this( typeName, null, filter, Query.DEFAULT_MAX, properties, null ); } /** * Constructor that sets all fields. * * @param typeName the name of the featureType to retrieve. * @param filter the OGC filter to constrain the request. * @param maxFeatures the maximum number of features to be returned. * @param propNames an array of the properties to fetch. * @param handle the name to associate with the query. */ public DefaultQuery(String typeName, Filter filter, int maxFeatures, String[] propNames, String handle) { this(typeName, null, filter, maxFeatures, propNames, handle ); } /** * Constructor that sets all fields. * * @param typeName the name of the featureType to retrieve. * @param namespace Namespace for provided typeName, or null if unspecified * @param filter the OGC filter to constrain the request. * @param maxFeatures the maximum number of features to be returned. * @param propNames an array of the properties to fetch. * @param handle the name to associate with the query. */ public DefaultQuery(String typeName, URI namespace, Filter filter, int maxFeatures, String[] propNames, String handle) { this.typeName = typeName; this.filter = filter; this.namespace = namespace; this.properties = propNames; this.maxFeatures = maxFeatures; this.handle = handle; } /** * Copy contructor, clones the state of a generic Query into a DefaultQuery * @param query */ public DefaultQuery(Query query) { this(query.getTypeName(), query.getNamespace(), query.getFilter(), query.getMaxFeatures(), query.getPropertyNames(), query.getHandle()); this.sortBy = query.getSortBy(); this.coordinateSystem = query.getCoordinateSystem(); this.coordinateSystemReproject = query.getCoordinateSystemReproject(); this.version = query.getVersion(); this.hints = query.getHints(); this.startIndex = query.getStartIndex(); } /** * The property names is used to specify the attributes that should be * selected for the return feature collection. If the property array is * null, then the datasource should return all available properties, its * full schema. If an array of specified then the full schema should be * used (all property names). The property names can be determined with a * getSchema call from the DataSource interface. * * <p> * This replaces our funky setSchema method of retrieving select * properties. I think it makes it easier to understand how to get * certain properties out of the datasource, instead of having users get * the schema and then compose a new schema using the attributes that * they want. The old way was also giving me problems because I couldn't * have multiple object reuse the same datasource object, since some other * object could come along and change its schema, and would then return * the wrong properties. * </p> * * <p></p> * * @return the property names to be used in the returned FeatureCollection. */ public String[] getPropertyNames() { return properties; } /** * Sets the properties to retrieve from the db. If the boolean to load all * properties is set to true then the AttributeTypes that are not in the * database's schema will just be filled with null values. * * @param propNames The names of attributes to load from the datasouce. */ public void setPropertyNames(String[] propNames) { this.properties = propNames; } /** * Sets the proper attributeTypes constructed from a schema and a list of * propertyNames. * * @param propNames the names of the properties to check against the * schema. If null then all attributes will be returned. If a List * of size 0 is used then only the featureIDs should be used. * * @task REVISIT: This syntax is really obscure. Consider having an fid or * featureID propertyName that datasource implementors look for * instead of looking to see if the list size is 0. */ public void setPropertyNames(List propNames) { if (propNames == null) { this.properties = null; return; } String[] stringArr = new String[propNames.size()]; this.properties = (String[]) propNames.toArray(stringArr); } /** * Convenience method to determine if the query should use the full schema * (all properties) of the data source for the features returned. This * method is equivalent to if (query.getProperties() == null), but allows * for more clarity on the part of datasource implementors, so they do not * need to examine and use null values. All Query implementations should * return true for this function if getProperties returns null. * * @return if all datasource attributes should be included in the schema * of the returned FeatureCollection. */ public boolean retrieveAllProperties() { return properties == null; } /** * The optional maxFeatures can be used to limit the number of features * that a query request retrieves. If no maxFeatures is specified then * all features should be returned. * * <p> * This is the only method that is not directly out of the Query element in * the WFS spec. It is instead a part of a <code>getFeatures</code> * request, which can hold one or more queries. But each of those in turn * will need a maxFeatures, so it is needed here. * </p> * * @return the max features the getFeature call should return. */ public int getMaxFeatures() { return this.maxFeatures; } public Integer getStartIndex(){ return this.startIndex; } public void setStartIndex(Integer startIndex){ if(startIndex != null && startIndex.intValue() < 0){ throw new IllegalArgumentException("startIndex shall be a positive integer: " + startIndex); } this.startIndex = startIndex; } /** * Sets the max features to retrieved by this query. * * @param maxFeatures the maximum number of features the getFeature call * should return. */ public void setMaxFeatures(int maxFeatures) { this.maxFeatures = maxFeatures; } /** * The Filter can be used to define constraints on a query. If no Filter * is present then the query is unconstrained and all feature instances * should be retrieved. * * @return The filter that defines constraints on the query. */ public Filter getFilter() { return this.filter; } /** * Sets the filter to constrain the query. * * @param filter the OGC filter to limit the datasource getFeatures * request. */ public void setFilter(Filter filter) { this.filter = filter; } /** * The typeName attribute is used to indicate the name of the feature type * to be queried. * * <p> * The DataStore API does not assume one feature type per datastore. * It currently makes use of this field to to specify with each request * what type to get. * </p> * @return the name of the feature type to be returned with this query. */ public String getTypeName() { return this.typeName; } /* (non-Javadoc) * @see org.geotools.data.Query#getNamespace() */ public URI getNamespace() { return namespace; } /** * Sets the typename. * * @param typeName the name of the featureType to retrieve. */ public void setTypeName(String typeName) { this.typeName = typeName; } /** * Set the namespace of the type name. * * @param namespace namespace of the type name */ public void setNamespace(URI namespace) { this.namespace = namespace; } /** * The handle attribute is included to allow a client to associate a * mnemonic name to the Query request. The purpose of the handle attribute * is to provide an error handling mechanism for locating a statement * that might fail. * * @return the mnemonic name of the query request. */ public String getHandle() { return this.handle; } /** * Sets a mnemonic name for the query request. * * @param handle the name to refer to this query. */ public void setHandle(String handle) { this.handle = handle; } /** * From WFS Spec: The version attribute is included in order to * accommodate systems that support feature versioning. A value of ALL * indicates that all versions of a feature should be fetched. Otherwise * an integer, n, can be specified to return the n th version of a * feature. The version numbers start at '1' which is the oldest version. * If a version value larger than the largest version is specified then * the latest version is return. The default action shall be for the query * to return the latest version. Systems that do not support versioning * can ignore the parameter and return the only version that they have. * * <p> * This is ready for use, it will be up to data store implementors to * support it. * </p> * * @return the version of the feature to return, or null for latest. */ public String getVersion() { return version; } /** * @see #getVersion() * @param version * @since 2.4 */ public void setVersion(String version) { this.version = version; } /** * Hashcode based on propertyName, maxFeatures and filter. * * @return hascode for filter */ public int hashCode() { String[] n = getPropertyNames(); return ((n == null) ? (-1) : ((n.length == 0) ? 0 : (n.length | n[0].hashCode()))) | getMaxFeatures() | ((getFilter() == null) ? 0 : getFilter().hashCode()) | ((getTypeName() == null) ? 0 : getTypeName().hashCode()) | ((getVersion() == null) ? 0 : getVersion().hashCode()) | ((getCoordinateSystem() == null) ? 0 : getCoordinateSystem().hashCode()) | ((getCoordinateSystemReproject() == null) ? 0 : getCoordinateSystemReproject().hashCode()) | getStartIndex(); } /** * Equality based on propertyNames, maxFeatures, filter, typeName and * version. * * <p> * Changing the handle does not change the meaning of the Query. * </p> * * @param obj Other object to compare against * * @return <code>true</code> if <code>obj</code> matches this filter */ public boolean equals(Object obj) { if ((obj == null) || !(obj instanceof Query)) { return false; } if (this == obj) return true; Query other = (Query) obj; return Arrays.equals(getPropertyNames(), other.getPropertyNames()) && (retrieveAllProperties() == other.retrieveAllProperties()) && (getMaxFeatures() == other.getMaxFeatures()) && ((getFilter() == null) ? (other.getFilter() == null) : getFilter().equals(other.getFilter())) && ((getTypeName() == null) ? (other.getTypeName() == null) : getTypeName().equals(other.getTypeName())) && ((getVersion() == null) ? (other.getVersion() == null) : getVersion().equals(other.getVersion())) && ((getCoordinateSystem() == null) ? (other.getCoordinateSystem() == null) : getCoordinateSystem().equals(other.getCoordinateSystem())) && ((getCoordinateSystemReproject() == null) ? (other.getCoordinateSystemReproject() == null) : getCoordinateSystemReproject().equals(other.getCoordinateSystemReproject())) && (getStartIndex() == other.getStartIndex()); } /** * Over ride of toString * * @return a string representation of this query object. */ public String toString() { StringBuffer returnString = new StringBuffer("Query:"); if (handle != null) { returnString.append(" [" + handle + "]"); } returnString.append("\n feature type: " + typeName); if (filter != null) { returnString.append("\n filter: " + filter.toString()); } returnString.append("\n [properties: "); if ((properties == null) || (properties.length == 0)) { returnString.append(" ALL ]"); } else { for (int i = 0; i < properties.length; i++) { returnString.append(properties[i]); if (i < (properties.length - 1)) { returnString.append(", "); } } returnString.append("]"); } if(sortBy != null && sortBy.length > 0) { returnString.append("\n [sort by: "); for (int i = 0; i < sortBy.length; i++) { returnString.append(sortBy[i].getPropertyName().getPropertyName()); returnString.append(" "); returnString.append(sortBy[i].getSortOrder().name()); if (i < (sortBy.length - 1)) { returnString.append(", "); } } returnString.append("]"); } return returnString.toString(); } /** * getCoordinateSystem purpose. * <p> * Description ... * </p> */ public CoordinateReferenceSystem getCoordinateSystem() { return coordinateSystem; } /** * getCoordinateSystemReproject purpose. * <p> * Description ... * </p> */ public CoordinateReferenceSystem getCoordinateSystemReproject() { return coordinateSystemReproject; } /** * setCoordinateSystem purpose. * <p> * Description ... * </p> * @param system */ public void setCoordinateSystem(CoordinateReferenceSystem system) { coordinateSystem = system; } /** * setCoordinateSystemReproject purpose. * <p> * Description ... * </p> * @param system */ public void setCoordinateSystemReproject(CoordinateReferenceSystem system) { coordinateSystemReproject = system; } /** * Retrive list of SortBy information. * <p> * Note we are using SortBy2, to be standards complient * you may limit yourself to to SortBy information. * </p> */ public SortBy[] getSortBy() { return sortBy; } /** * Sets the sort by information. * */ public void setSortBy(SortBy[] sortBy) { this.sortBy = sortBy; } public Hints getHints() { if(hints == null) hints = new Hints(Collections.EMPTY_MAP); return hints; } /** * Sets the query hints * @param hints */ public void setHints(Hints hints) { this.hints = hints; } }