/* * 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. * * Created on 27.11.2003 */ package org.geotools.gml.producer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.List; import java.util.logging.Logger; import org.geotools.filter.LengthFunction; import org.geotools.xml.transform.TransformerBase; import org.geotools.xml.transform.Translator; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.filter.BinaryComparisonOperator; import org.opengis.filter.Filter; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Transformer that transforms feature types into (hopefully) valid XML * schemas. This class may be used by geoserver in the future to automatically * create XML schemas for the DescribeFeatureType request. * <pre> * FeatureType type = ...; // you have it from somewhere * FeatureTypeTransformer t = new FeatureTypeTransformer(); * t.transform(type, System.out); * </pre> * The following table lists the mapping between java types and xml schema data * types for attribute types. * * <table> * <tr><th> * Java * </td><th> * XML Schema * </td></tr> * <tr><td> * String * </td><td> * xs:string * </td></tr> * <tr><td> * Byte * </td><td> * xs:byte * </td></tr> * <tr><td> * Short * </td><td> * xs:short * </td></tr> * <tr><td> * Integer * </td><td> * xs:int * </td></tr> * <tr><td> * Long * </td><td> * xs:long * </td></tr> * <tr><td> * BigInteger * </td><td> * xs:integer * </td></tr> * <tr><td> * BigDecimal * </td><td> * xs:decimal * </td></tr> * <tr><td> * java.util.Date * </td><td> * xs:dateTime * </td></tr> * <tr><td> * java.sql.Date * </td><td> * xs:date * </td></tr> * <tr><td> * java.sql.Time * </td><td> * xs:time * </td></tr> * <tr><td> * java.sql.Timestamp * </td><td> * xs:dateTime * </td></tr> * <tr><td> * java.lang.Boolean * </td><td> * xs:boolean * </td></tr> * <tr><td> * com.vividsolutions.jts.geom.Point * </td><td> * gml:PointPropertyType * </td></tr> * <tr><td> * com.vividsolutions.jts.geom.LineString * </td><td> * gml:LineStringPropertyType * </td></tr> * <tr><td> * com.vividsolutions.jts.geom.Polygon * </td><td> * gml:PolygonPropertyType * </td></tr> * <tr><td> * com.vividsolutions.jts.geom.MultiPoint * </td><td> * gml:MultiPointPropertyType * </td></tr> * <tr><td> * com.vividsolutions.jts.geom.MultiLineString * </td><td> * gml:MutliLineStringPropertyType * </td></tr> * <tr><td> * com.vividsolutions.jts.geom.MultiPolygon * </td><td> * gml:MultiPolygonPropertyType * </td></tr> * <tr><td> * org.geotools.data.Feature * </td><td> * gml:AbstractFeatureType * </td></tr> * </table> * * @author Simon Raess * * @source $URL$ * @version $Id$ * * @task TODO: Support GeometryCollection. * @task REVISIT: Should support a bit more for the header, like being able to * set the schemaLocation. It also should declare and import the gml * namespace - it's always used as all extend gml:AbstractFeatureType. * Also should be able to set the global substitution group, so that * this type can be used directly as a feature. */ public class FeatureTypeTransformer extends TransformerBase { private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger( "org.geotools.gml.producer.FeatureTypeTransformer"); private static final String SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; /** * Creates a Translator that is capable to translate FeatureType objects * into a XML schema fragment. * * @param handler the content handler to use * * @return DOCUMENT ME! */ public Translator createTranslator(ContentHandler handler) { FeatureTypeTranslator translator = new FeatureTypeTranslator(handler); return translator; } /** * A FeatureTypeTranslator encodes FeatureTypes as a (hopefully) valid XML * schema. * * @see TransformerBase.TranslatorSupport */ public static class FeatureTypeTranslator extends TransformerBase.TranslatorSupport { /** * Creates a new FeatureTypeTranslator. The default prefix is "xs" and * the default namespace is "http://www.w3.org/2001/XMLSchema". * * @param handler the content handler that receives the SAX events */ public FeatureTypeTranslator(ContentHandler handler) { super(handler, "xs", "http://www.w3.org/2001/XMLSchema"); } /** * Encode object o, which must be an instance of FeatureType. If it is * not an IllegalArgumentException will be thrown. * * @param o DOCUMENT ME! * * @throws IllegalArgumentException if supplied object is not an * instance of FeatureType * * @see org.geotools.xml.transform.Translator#encode(java.lang.Object) */ public void encode(Object o) throws IllegalArgumentException { if (o instanceof SimpleFeatureType) { encode((SimpleFeatureType) o); } else { throw new IllegalArgumentException( "Translator does not know how to translate " + o.getClass().getName()); } } /** * Encode the supplied feature type. * * @param type the feature type to encode * * @throws RuntimeException DOCUMENT ME! */ protected void encode(SimpleFeatureType type) { List attributes = type.getAttributeDescriptors(); try { startSchemaType(type.getTypeName(), type.getName().getNamespaceURI()); for (int i = 0; i < attributes.size(); i++) { encode((AttributeDescriptor) attributes.get(i)); } endSchemaType(); } catch (SAXException e) { throw new RuntimeException(e); } } /** * Start the schema fragment for a feature type. * * @param name * @param namespace DOCUMENT ME! * * @throws SAXException */ protected final void startSchemaType(String name, String namespace) throws SAXException { AttributesImpl atts = new AttributesImpl(); atts.addAttribute("", "name", "name", "", name + "_Type"); contentHandler.startElement(SCHEMA_NS, "complexType", "xs:complexType", atts); contentHandler.startElement(SCHEMA_NS, "complexContent", "xs:complexContent", new AttributesImpl()); atts = new AttributesImpl(); atts.addAttribute("", "base", "base", "", "gml:AbstractFeatureType"); contentHandler.startElement(SCHEMA_NS, "extension", "xs:extension", atts); contentHandler.startElement(SCHEMA_NS, "sequence", "xs:sequence", new AttributesImpl()); } /** * End the schema fragment for a feature type. * * @throws SAXException */ protected void endSchemaType() throws SAXException { contentHandler.endElement(SCHEMA_NS, "sequence", "xs:sequence"); contentHandler.endElement(SCHEMA_NS, "extension", "xs:extension"); contentHandler.endElement(SCHEMA_NS, "complexContent", "xs:complexContent"); contentHandler.endElement(SCHEMA_NS, "complexType", "xs:complexType"); } /** * Encode an AttributeType. * * @param attribute * * @throws SAXException * @throws RuntimeException DOCUMENT ME! */ protected void encode(AttributeDescriptor attribute) throws SAXException { Class type = attribute.getType().getBinding(); if (Number.class.isAssignableFrom(type)) { encodeNumber(attribute); } else if (Date.class.isAssignableFrom(type)) { encodeDate(attribute); } else if (type == String.class) { encodeString(attribute); } else if (Geometry.class.isAssignableFrom(type)) { encodeGeometry(attribute); } else if (type == Boolean.class) { encodeBoolean(attribute); /*} else if (FeatureType.class.isAssignableFrom(type)) { encodeFeature(attribute);*/ } else { throw new RuntimeException("Cannot encode " + type.getName()); } } /** * Encode an AttributeType whose value type is a Boolean. * * @param attribute * * @throws SAXException */ protected void encodeBoolean(AttributeDescriptor attribute) throws SAXException { AttributesImpl atts = createStandardAttributes(attribute); atts.addAttribute("", "type", "type", "", "xs:boolean"); contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); } /** * Encode an AttributeType whose value type is a String. * * @param attribute the attribute to encode * * @throws SAXException */ protected void encodeString(AttributeDescriptor attribute) throws SAXException { int length = Integer.MAX_VALUE; for ( Filter f : attribute.getType().getRestrictions() ) { if ( f == Filter.INCLUDE || f == Filter.EXCLUDE ) continue; try{ if(f instanceof PropertyIsLessThan || f instanceof PropertyIsLessThanOrEqualTo ){ BinaryComparisonOperator cf = (BinaryComparisonOperator) f; Expression e = cf.getExpression1(); if(e!= null && e instanceof LengthFunction){ length = Integer.parseInt(((Literal)cf.getExpression2()).getValue().toString()); }else{ if(cf.getExpression2() instanceof LengthFunction){ length = Integer.parseInt(((Literal)cf.getExpression1()).getValue().toString()); } } } }catch(Throwable t){ length = Integer.MAX_VALUE; } } AttributesImpl atts = createStandardAttributes(attribute); if (length == 0) { atts.addAttribute("", "type", "type", "", "xs:string"); contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); } else { contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); contentHandler.startElement(SCHEMA_NS, "simpleType", "xs:simpleType", new AttributesImpl()); atts = new AttributesImpl(); atts.addAttribute("", "base", "base", "", "xs:string"); contentHandler.startElement(SCHEMA_NS, "restriction", "xs:restriction", atts); atts = new AttributesImpl(); atts.addAttribute("", "value", "value", "", "" + length); contentHandler.startElement(SCHEMA_NS, "maxLength", "xs:maxLength", atts); contentHandler.endElement(SCHEMA_NS, "maxLength", "xs:maxLength"); contentHandler.endElement(SCHEMA_NS, "restriction", "xs:restriction"); contentHandler.endElement(SCHEMA_NS, "simpleType", "xs:simpleType"); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); } } /** * Encode an AttributeType whose value type is a Number. * * @param attribute * * @throws SAXException * @throws RuntimeException DOCUMENT ME! */ protected void encodeNumber(AttributeDescriptor attribute) throws SAXException { AttributesImpl atts = createStandardAttributes(attribute); Class type = attribute.getType().getBinding(); String typeString; if (type == Byte.class) { typeString = "xs:byte"; } else if (type == Short.class) { typeString = "xs:short"; } else if (type == Integer.class) { typeString = "xs:int"; } else if (type == Long.class) { typeString = "xs:long"; } else if (type == Float.class) { typeString = "xs:float"; } else if (type == Double.class) { typeString = "xs:double"; } else if (type == BigInteger.class) { typeString = "xs:integer"; } else if (type == BigDecimal.class) { typeString = "xs:decimal"; } else if (Number.class.isAssignableFrom(type)) { typeString = "xs:decimal"; } else { throw new RuntimeException( "Called encode number with invalid attribute type."); } atts.addAttribute("", "type", "type", "", typeString); contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); } /** * Encode an AttributeType whose value type is a Date. * * @param attribute * * @throws SAXException */ protected void encodeDate(AttributeDescriptor attribute) throws SAXException { AttributesImpl atts = createStandardAttributes(attribute); Class binding = attribute.getType().getBinding(); if(java.sql.Date.class.isAssignableFrom(binding)) atts.addAttribute("", "type", "type", "", "xs:date"); else if(java.sql.Time.class.isAssignableFrom(binding)) atts.addAttribute("", "type", "type", "", "xs:time"); else atts.addAttribute("", "type", "type", "", "xs:dateTime"); contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); } /** * Encode an AttributeType whose value type is a Geometry. * * @param attribute * * @throws SAXException * @throws RuntimeException DOCUMENT ME! */ protected void encodeGeometry(AttributeDescriptor attribute) throws SAXException { AttributesImpl atts = createStandardAttributes(attribute); Class type = attribute.getType().getBinding(); String typeString = ""; LOGGER.finer(type.getName()); if (type == Point.class) { typeString = "gml:PointPropertyType"; } else if (type == LineString.class) { typeString = "gml:LineStringPropertyType"; } else if (type == Polygon.class) { typeString = "gml:PolygonPropertyType"; } else if (type == MultiPoint.class) { typeString = "gml:MultiPointPropertyType"; } else if (type == MultiLineString.class) { typeString = "gml:MultiLineStringPropertyType"; } else if (type == MultiPolygon.class) { typeString = "gml:MultiPolygonPropertyType"; } else if (type == GeometryCollection.class) { typeString = "gml:MultiGeometryPropertyType"; } else if (type == Geometry.class) { typeString = "gml:GeometryAssociationType"; } else { throw new RuntimeException("Unsupported type: " + type.getName()); } atts.addAttribute("", "type", "type", "", typeString); contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); } /** * Encode an AttributeType whose value type is a Feature. * * @param attribute * */ /*protected void encodeFeature(AttributeType attribute) throws SAXException { AttributesImpl atts = createStandardAttributes(attribute); //atts.addAttribute("", "type", "type", "", "gml:AbstractFeatureType"); contentHandler.startElement(SCHEMA_NS, "element", "xs:element", atts); encode(""); contentHandler.endElement(SCHEMA_NS, "element", "xs:element"); }*/ /** * Creates standard xml attributes present on all xs:element elements. * These are name, maxOccurs, minOccurs and nillable. * * @param attribute the attribute type from which the information is * retrieved * * @return an org.xml.sax.helpers.AttributesImpl object that contains * the standard attributes */ protected AttributesImpl createStandardAttributes( AttributeDescriptor attribute) { AttributesImpl atts = new AttributesImpl(); atts.addAttribute("", "name", "name", "", attribute.getLocalName()); if (attribute.isNillable() && attribute.getMinOccurs() == 0) { atts.addAttribute("", "minOccurs", "minOccurs", "", "0"); atts.addAttribute("", "nillable", "nillable", "", "true"); } else { atts.addAttribute("", "minOccurs", "minOccurs", "", "1"); atts.addAttribute("", "nillable", "nillable", "", "false"); } return atts; } } }