/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.filter.expression;
import java.util.Enumeration;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathIntrospector;
import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
import org.geotools.factory.Hints;
import org.geotools.factory.Hints.Key;
import org.geotools.feature.AttributeImpl;
import org.geotools.feature.ComplexAttributeImpl;
import org.geotools.feature.FeatureImpl;
import org.geotools.feature.GeometryAttributeImpl;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.simple.SimpleFeatureImpl;
import org.geotools.feature.type.AttributeDescriptorImpl;
import org.geotools.feature.type.FeatureTypeImpl;
import org.geotools.feature.xpath.AttributeDescriptorPropertyHandler;
import org.geotools.feature.xpath.AttributeNodePointerFactory;
import org.geotools.feature.xpath.AttributePropertyHandler;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.Feature;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.ComplexType;
import org.opengis.feature.type.FeatureType;
import org.xml.sax.helpers.NamespaceSupport;
import com.vividsolutions.jts.geom.Geometry;
/**
* Creates a namespace aware property accessor for ISO Features.
* <p>
* The created accessor handles a small subset of xpath expressions, a non-nested "name" which
* corresponds to a feature attribute, and "@id", corresponding to the feature id.
* </p>
* <p>
* THe property accessor may be run against {@link org.geotools.feature.Feature}, or against
* {@link org.geotools.feature.FeatureType}. In the former case the feature property value is
* returned, in the latter the feature property type is returned.
* </p>
*
* @author Justin Deoliveira, The Open Planning Project
* @author Gabriel Roldan, Axios Engineering
*
*
* @source $URL$
*/
public class FeaturePropertyAccessorFactory implements PropertyAccessorFactory {
/**
* {@link Hints} key used to pass namespace context to
* {@link #createPropertyAccessor(Class, String, Class, Hints)} in the form of a
* {@link NamespaceSupport} instance with the prefix/namespaceURI mappings
*/
public static final Key NAMESPACE_CONTEXT = new Hints.Key(
org.xml.sax.helpers.NamespaceSupport.class);
static {
// unfortunatley, jxpath only works against concreate classes
// JXPathIntrospector.registerDynamicClass(DefaultFeature.class,
// FeaturePropertyHandler.class);
JXPathContextReferenceImpl.addNodePointerFactory(new AttributeNodePointerFactory());
}
/** Single instnace is fine - we are not stateful */
static PropertyAccessor ATTRIBUTE_ACCESS = new FeaturePropertyAccessor();
static PropertyAccessor DEFAULT_GEOMETRY_ACCESS = new DefaultGeometryFeaturePropertyAccessor();
static PropertyAccessor FID_ACCESS = new FidFeaturePropertyAccessor();
public PropertyAccessor createPropertyAccessor(Class type, String xpath, Class target,
Hints hints) {
if (SimpleFeature.class.isAssignableFrom(type)) {
/*
* This class is not intended for use with SimpleFeature and causes problems when
* discovered via SPI and used by code expecting SimpleFeature behaviour. In particular
* WMS styling code may fail when this class is present. See GEOS-3525.
*/
return null;
}
if (xpath == null)
return null;
if (!ComplexAttribute.class.isAssignableFrom(type)
&& !ComplexType.class.isAssignableFrom(type)
&& !AttributeDescriptor.class.isAssignableFrom(type))
return null;
if ("".equals(xpath) && target == Geometry.class)
return DEFAULT_GEOMETRY_ACCESS;
// check for fid access
if (xpath.matches("@(\\w+:)?id"))
return FID_ACCESS;
// check for simple property access
// if (xpath.matches("(\\w+:)?(\\w+)")) {
NamespaceSupport namespaces = null;
if (hints != null) {
namespaces = (NamespaceSupport) hints
.get(FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT);
}
if (namespaces == null) {
return ATTRIBUTE_ACCESS;
} else {
return new FeaturePropertyAccessor(namespaces);
}
// }
// return null;
}
/**
* We strip off namespace prefix, we need new feature model to do this property
* <ul>
* <li>BEFORE: foo:bar
* <li>AFTER: bar
* </ul>
*
* @param xpath
* @return xpath with any XML prefixes removed
*/
static String stripPrefix(String xpath) {
int split = xpath.indexOf(":");
if (split != -1) {
return xpath.substring(split + 1);
}
return xpath;
}
/**
* Access to Feature Identifier.
*
* @author Jody Garnett, Refractions Research Inc.
*/
static class FidFeaturePropertyAccessor implements PropertyAccessor {
public boolean canHandle(Object object, String xpath, Class target) {
// we only work against feature, not feature type
return object instanceof Attribute && xpath.matches("@(\\w+:)?id");
}
public Object get(Object object, String xpath, Class target) {
Attribute feature = (Attribute) object;
return feature.getIdentifier().toString();
}
public void set(Object object, String xpath, Object value, Class target) {
throw new org.opengis.feature.IllegalAttributeException(null, value,
"feature id is immutable");
}
}
static class DefaultGeometryFeaturePropertyAccessor implements PropertyAccessor {
public boolean canHandle(Object object, String xpath, Class target) {
if (!"".equals(xpath))
return false;
if (target != Geometry.class || target != GeometryAttribute.class)
return false;
return (object instanceof Feature || object instanceof FeatureType);
}
public Object get(Object object, String xpath, Class target) {
if (object instanceof Feature) {
return ((Feature) object).getDefaultGeometryProperty();
}
// no equivalent in geoapi-2.2?
//
// if (object instanceof FeatureType) {
// return ((FeatureType) object).getDefaultGeometry();
// }
return null;
}
public void set(Object object, String xpath, Object value, Class target)
throws IllegalAttributeException {
if (object instanceof Feature) {
final Feature f = (Feature) object;
GeometryAttribute geom;
if (value instanceof GeometryAttribute) {
geom = (GeometryAttribute) value;
f.setDefaultGeometryProperty(geom);
} else if (value instanceof Geometry) {
geom = f.getDefaultGeometryProperty();
geom.setValue(value);
} else {
throw new IllegalArgumentException("Argument is not a geometry: " + value);
}
}
if (object instanceof FeatureType) {
throw new IllegalAttributeException("feature type is immutable");
}
}
}
static class FeaturePropertyAccessor implements PropertyAccessor {
static {
// TODO: use a wrapper public class for Feature in order to
// support any implementation. Reason being that JXPath works
// over concrete classes and hence we cannot set it up over the
// interface
JXPathIntrospector.registerDynamicClass(FeatureImpl.class,
AttributePropertyHandler.class);
JXPathIntrospector.registerDynamicClass(SimpleFeatureImpl.class,
AttributePropertyHandler.class);
JXPathIntrospector.registerDynamicClass(ComplexAttributeImpl.class,
AttributePropertyHandler.class);
JXPathIntrospector.registerDynamicClass(AttributeImpl.class,
AttributePropertyHandler.class);
JXPathIntrospector.registerDynamicClass(GeometryAttributeImpl.class,
AttributePropertyHandler.class);
// JXPathIntrospector.registerDynamicClass(BooleanAttribute.class,
// AttributePropertyHandler.class);
// JXPathIntrospector.registerDynamicClass(NumericAttribute.class,
// AttributePropertyHandler.class);
// JXPathIntrospector.registerDynamicClass(TemporalAttribute.class,
// AttributePropertyHandler.class);
// JXPathIntrospector.registerDynamicClass(TextualAttribute.class,
// AttributePropertyHandler.class);
JXPathIntrospector.registerDynamicClass(AttributeDescriptorImpl.class,
AttributeDescriptorPropertyHandler.class);
JXPathIntrospector.registerDynamicClass(FeatureTypeImpl.class,
AttributeDescriptorPropertyHandler.class);
}
private NamespaceSupport namespaces;
public FeaturePropertyAccessor() {
namespaces = new NamespaceSupport();
}
public FeaturePropertyAccessor(NamespaceSupport namespaces) {
this.namespaces = namespaces;
}
public boolean canHandle(Object object, String xpath, Class target) {
// xpath = stripPrefix(xpath);
return object instanceof Attribute || object instanceof AttributeType
|| object instanceof AttributeDescriptor;
}
public Object get(Object object, String xpath, Class target) {
// xpath = stripPrefix(xpath);
JXPathContext context = JXPathContext.newContext(object);
context.setLenient(true);
Enumeration declaredPrefixes = namespaces.getDeclaredPrefixes();
while (declaredPrefixes.hasMoreElements()) {
String prefix = (String) declaredPrefixes.nextElement();
String uri = namespaces.getURI(prefix);
context.registerNamespace(prefix, uri);
}
Object value = context.getValue(xpath);
return value;
}
public void set(Object object, String xpath, Object value, Class target)
throws IllegalAttributeException {
// xpath = stripPrefix(xpath);
if (object instanceof FeatureType) {
throw new IllegalAttributeException("feature type is immutable");
}
JXPathContext context = JXPathContext.newContext(object);
context.setLenient(true);
context.setValue(xpath, value);
assert value == context.getValue(xpath);
}
}
}