/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.wfs.xml; import static org.geoserver.ows.util.ResponseUtils.buildURL; import static org.geoserver.ows.util.ResponseUtils.params; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.xsd.XSDComplexTypeDefinition; import org.eclipse.xsd.XSDCompositor; import org.eclipse.xsd.XSDDerivationMethod; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDFactory; import org.eclipse.xsd.XSDForm; import org.eclipse.xsd.XSDImport; import org.eclipse.xsd.XSDInclude; import org.eclipse.xsd.XSDModelGroup; import org.eclipse.xsd.XSDNamedComponent; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.XSDSchemaContent; import org.eclipse.xsd.XSDTypeDefinition; import org.eclipse.xsd.impl.XSDSchemaImpl; import org.eclipse.xsd.util.XSDConstants; import org.eclipse.xsd.util.XSDSchemaLocator; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.config.GeoServer; import org.geoserver.ows.URLMangler.URLType; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.wfs.GMLInfo; import org.geoserver.wfs.WFSInfo; import org.geotools.feature.NameImpl; import org.geotools.gml2.GMLConfiguration; import org.geotools.gml3.v3_2.GML; import org.geotools.wfs.v2_0.WFS; import org.geotools.xml.Configuration; import org.geotools.xml.Schemas; import org.geotools.xs.XS; 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.opengis.feature.type.Name; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.feature.type.Schema; /** * Builds a {@link org.eclipse.xsd.XSDSchema} from {@link FeatureTypeInfo} * metadata objects. * <p> * * </p> * * @author Justin Deoliveira, The Open Planning Project * @author Ben Caradoc-Davies, CSIRO Exploration and Mining */ public abstract class FeatureTypeSchemaBuilder { /** logging instance */ static Logger logger = org.geotools.util.logging.Logging.getLogger("org.geoserver.wfs"); /** wfs configuration */ WFSInfo wfs; /** the catalog */ Catalog catalog; /** resource loader */ GeoServerResourceLoader resourceLoader; /** * profiles used for type mapping. */ protected List profiles; /** * gml schema stuff */ protected String gmlNamespace; protected String gmlSchemaLocation; protected String baseType; protected String substitutionGroup; protected Map<String, String> describeFeatureTypeParams; protected String gmlPrefix; protected Configuration xmlConfiguration; protected volatile XSDElementDeclaration featureSubGroupElement; protected FeatureTypeSchemaBuilder(GeoServer gs) { this.wfs = gs.getService( WFSInfo.class ); this.catalog = gs.getCatalog(); this.resourceLoader = gs.getCatalog().getResourceLoader(); profiles = new ArrayList(); profiles.add(new XSProfile()); } public Map<String, String> getDescribeFeatureTypeParams() { return describeFeatureTypeParams; } public Configuration getXmlConfiguration() { return xmlConfiguration; } public XSDSchema build(FeatureTypeInfo featureTypeInfo, String baseUrl) throws IOException { return build(new FeatureTypeInfo[] { featureTypeInfo }, baseUrl); } public XSDSchema build(FeatureTypeInfo[] featureTypeInfos, String baseUrl) throws IOException { return build(featureTypeInfos, baseUrl, true); } public XSDSchema build(FeatureTypeInfo[] featureTypeInfos, String baseUrl, boolean scheduleSchemaCleanup) throws IOException { // build the schema and make sure to schedule it for destruction at the end of the request XSDSchema schema = buildSchemaInternal(featureTypeInfos, baseUrl); if(schema != null && scheduleSchemaCleanup) { SchemaCleanerCallback.addSchema(schema); } return schema; } public XSDSchema buildSchemaInternal(FeatureTypeInfo[] featureTypeInfos, String baseUrl) throws IOException { XSDFactory factory = XSDFactory.eINSTANCE; XSDSchema schema = factory.createXSDSchema(); schema.setSchemaForSchemaQNamePrefix("xsd"); schema.getQNamePrefixToNamespaceMap().put("xsd", XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001); schema.setElementFormDefault(XSDForm.get(XSDForm.QUALIFIED)); //group the feature types by namespace HashMap ns2featureTypeInfos = new HashMap(); for (int i = 0; i < featureTypeInfos.length; i++) { String prefix = featureTypeInfos[i].getNamespace().getPrefix(); List l = (List) ns2featureTypeInfos.get(prefix); if (l == null) { l = new ArrayList(); } l.add(featureTypeInfos[i]); ns2featureTypeInfos.put(prefix, l); } if (baseUrl == null) baseUrl = wfs.getSchemaBaseURL(); if (ns2featureTypeInfos.entrySet().size() == 1) { // only 1 namespace, write target namespace out String targetPrefix = (String) ns2featureTypeInfos.keySet().iterator().next(); String targetNamespace = catalog.getNamespaceByPrefix(targetPrefix).getURI(); schema.setTargetNamespace(targetNamespace); schema.getQNamePrefixToNamespaceMap().put(targetPrefix, targetNamespace); // add secondary namespaces from catalog for (NamespaceInfo nameSpaceinfo : catalog.getNamespaces()) { if (!schema.getQNamePrefixToNamespaceMap().containsKey(nameSpaceinfo.getPrefix())) { schema.getQNamePrefixToNamespaceMap().put(nameSpaceinfo.getPrefix(), nameSpaceinfo.getURI()); } } // would result in some xsd:include or xsd:import if schema location is specified try { FeatureType featureType = featureTypeInfos[0].getFeatureType(); Object schemaUri = featureType.getUserData().get("schemaURI"); if (schemaUri != null && schemaUri instanceof Map) { // should always be a Map.. set in AppSchemaDataAccessConfigurator // impose iteration order @SuppressWarnings("unchecked") Map<String, String> schemaURIs = new TreeMap<String, String>( (Map<String, String>) schemaUri); // schema is supplied by the user.. just include the top level schema instead of // building the type if (!findTypeInSchema(featureTypeInfos[0], schema, factory)) { Map<String, String> imports = new HashMap<String, String>(); for (String namespace : schemaURIs.keySet()) { addReference(schema, factory, namespace, schemaURIs.get(namespace), imports); } } return schema; } } catch (IOException e) { logger.warning("Unable to get schema location for feature type '" + featureTypeInfos[0].getPrefixedName() + "'. Reason: '" + e.getMessage() + "'. Building the schema manually instead."); } // user didn't define schema location // import gml schema importGMLSchema(schema, factory, baseUrl); schema.getQNamePrefixToNamespaceMap().put(gmlPrefix, gmlNamespace); //schema.getQNamePrefixToNamespaceMap().put("gml", "http://www.opengis.net/gml"); // then manually build schema for (int i = 0; i < featureTypeInfos.length; i++) { try { buildSchemaContent(featureTypeInfos[i], schema, factory, baseUrl); } catch (Exception e) { logger.log(Level.WARNING, "Could not build xml schema for type: " + featureTypeInfos[i].getName(), e); } } } else { //different namespaces, write out import statements Map<String, String> imports = new HashMap<String, String>(); for (Iterator i = ns2featureTypeInfos.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); String prefix = (String) entry.getKey(); List types = (List) entry.getValue(); StringBuffer typeNames = new StringBuffer(); for (Iterator t = types.iterator(); t.hasNext();) { FeatureTypeInfo info = (FeatureTypeInfo) t.next(); FeatureType featureType = info.getFeatureType(); Object schemaUri = featureType.getUserData().get("schemaURI"); if (schemaUri != null && schemaUri instanceof Map) { // should always be a Map.. set in AppSchemaDataAccessConfigurator // impose iteration order @SuppressWarnings("unchecked") Map<String, String> schemaURIs = new TreeMap<String, String>( (Map<String, String>) schemaUri); // schema is supplied by the user.. just import the specified location for (String namespace : schemaURIs.keySet()) { addReference(schema, factory, namespace, schemaURIs.get(namespace), imports); } } else { typeNames.append(info.getPrefixedName()).append(","); } } if (typeNames.length() > 0) { typeNames.setLength(typeNames.length()-1); // schema not found, encode describe feature type URL Map<String, String> params = new LinkedHashMap<String, String>(describeFeatureTypeParams); params.put("typeName", typeNames.toString().trim()); String schemaLocation = buildURL(baseUrl, "wfs", params, URLType.RESOURCE); String namespace = catalog.getNamespaceByPrefix(prefix).getURI(); XSDImport imprt = factory.createXSDImport(); imprt.setNamespace(namespace); imprt.setSchemaLocation(schemaLocation); schema.getContents().add(imprt); } } } // for (Iterator i = schema.getContents().iterator(); i.hasNext();) { // Object o = i.next(); // if (o instanceof XSDImport) { // ((XSDSchemaImpl)((XSDImport)o).getResolvedSchema()).imported((XSDImport)o); // // } // } return schema; } protected void importGMLSchema(XSDSchema schema, XSDFactory factory, String baseUrl) { XSDImport imprt = factory.createXSDImport(); imprt.setNamespace(gmlNamespace); imprt.setSchemaLocation(ResponseUtils.buildSchemaURL(baseUrl, gmlSchemaLocation)); XSDSchema gmlSchema = gmlSchema(); imprt.setResolvedSchema(gmlSchema); schema.getContents().add(imprt); } /** * Add a schema reference using include or import. Duplicate conflicting imports are not allowed * (warning is logged). * * @param schema * schema to which the reference is being added * @param factory * used to make stuff * @param namespace * namespace URI of the referenced schema * @param schemaLocation * location URI of the referenced schema * @param imports * map of existing imports */ private void addReference(XSDSchema schema, XSDFactory factory, String namespace, String schemaLocation, Map<String, String> imports) { if (getWfsSchema() != null && imports.get(getWfsSchema().getTargetNamespace()) == null) { /* * Add an import for the WFS schema. Needed for GML32OutputFormat only. See * GML32.importGMLSchema where a similar import is used for simple features (generated * schema). * * Not that this does not break DescribeFeatureType because it uses WFS 1.1. */ schema.getQNamePrefixToNamespaceMap().put("wfs", getWfsSchema().getTargetNamespace()); XSDImport wfsImport = factory.createXSDImport(); wfsImport.setNamespace(getWfsSchema().getTargetNamespace()); wfsImport.setSchemaLocation(getWfsSchema().getSchemaLocation()); wfsImport.setResolvedSchema(getWfsSchema()); schema.getContents().add(wfsImport); ((XSDSchemaImpl)getWfsSchema()).imported(wfsImport); imports.put(getWfsSchema().getTargetNamespace(), getWfsSchema().getSchemaLocation()); } if (namespace.equals(schema.getTargetNamespace())) { // same namespace so include reference addInclude(schema, factory, namespace, schemaLocation); } else { // different namespace so import reference if (imports.get(namespace) != null) { if (imports.get(namespace).equals(schemaLocation)) { logger.finer("Skipped generation of duplicate import for namespace=\"" + namespace + "\" schemaLocation=\"" + schemaLocation + " while generating schema for " + schema.getTargetNamespace()); } else { logger.warning("Skipped generation of conflicting import for namespace=\"" + namespace + "\" schemaLocation=\"" + schemaLocation + " while generating schema for " + schema.getTargetNamespace() + " (some XML processors will ignore all imports for a namespace after the first)"); } } else { addImport(schema, factory, namespace, schemaLocation); imports.put(namespace, schemaLocation); } } } /** * Add include statement to schema. * * @param schema * Output schema * @param factory * XSD factory used to produce schema * @param schemaLocation * The schema location to be included */ private void addInclude(XSDSchema schema, XSDFactory factory, String namespace, String schemaLocation) { XSDInclude xsdInclude = factory.createXSDInclude(); xsdInclude.setSchemaLocation(schemaLocation); schema.getContents().add(xsdInclude); } /** * Add import statement to schema. * * @param schema * Output schema * @param factory * XSD factory used to produce schema * @param namespace * Import name space * @param schemaLocation * The schema to be imported */ private void addImport(XSDSchema schema, XSDFactory factory, String namespace, String schemaLocation) { XSDImport xsdImport = factory.createXSDImport(); xsdImport.setNamespace(namespace); xsdImport.setSchemaLocation((String) schemaLocation); schema.getContents().add(xsdImport); } /** * Return any additional WFS XSDSchema that the GML version might require, or null if not * required. * * @return the WFS schema, or null if none */ protected XSDSchema getWfsSchema() { return null; } /** * Adds types defined in the catalog to the provided schema. */ public XSDSchema addApplicationTypes( XSDSchema wfsSchema ) throws IOException { //incorporate application schemas into the wfs schema Collection<FeatureTypeInfo> featureTypeInfos = catalog.getFeatureTypes(); for (Iterator<FeatureTypeInfo> i = featureTypeInfos.iterator(); i.hasNext();) { FeatureTypeInfo meta = i.next(); // don't build schemas for disabled feature types if(!meta.enabled()) continue; //build the schema for the types in the single namespace (and don't clean them, they are not dynamic) XSDSchema schema = buildSchemaInternal(new FeatureTypeInfo[] { meta }, null); //declare the namespace String prefix = meta.getNamespace().getPrefix(); String namespaceURI = meta.getNamespace().getURI(); wfsSchema.getQNamePrefixToNamespaceMap().put(prefix, namespaceURI); //add the types + elements to the wfs schema for (Iterator<XSDTypeDefinition> t = schema.getTypeDefinitions().iterator(); t.hasNext();) { wfsSchema.getTypeDefinitions().add(t.next()); } for (Iterator<XSDElementDeclaration> e = schema.getElementDeclarations().iterator(); e.hasNext();) { wfsSchema.getElementDeclarations().add(e.next()); } // add secondary namespaces from catalog for (Map.Entry<String, String> entry : (Set<Map.Entry<String, String>>) schema.getQNamePrefixToNamespaceMap() .entrySet()) { if (!wfsSchema.getQNamePrefixToNamespaceMap().containsKey(entry.getKey())) { wfsSchema.getQNamePrefixToNamespaceMap().put(entry.getKey(), entry.getValue()); } } } return wfsSchema; } boolean findTypeInSchema(FeatureTypeInfo featureTypeMeta, XSDSchema schema, XSDFactory factory) throws IOException { // look if the schema for the type is already defined String ws = featureTypeMeta.getStore().getWorkspace().getName(); String ds = featureTypeMeta.getStore().getName(); String name = featureTypeMeta.getName(); File schemaFile = null; try { schemaFile = resourceLoader.find("workspaces/" + ws + "/" + ds + "/" + name + "/schema.xsd"); } catch (IOException e1) { } if (schemaFile != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Found customized schema.xsd: " + schemaFile.getAbsolutePath()); } //schema file found, parse it and lookup the complex type List resolvers = Schemas.findSchemaLocationResolvers(xmlConfiguration); List locators = new ArrayList(); locators.add( new XSDSchemaLocator() { public XSDSchema locateSchema(XSDSchema schema, String namespaceURI, String rawSchemaLocationURI, String resolvedSchemaLocationURI) { if ( gmlNamespace.equals( namespaceURI ) ) { return gmlSchema(); } return null; } }); XSDSchema ftSchema = null; try { ftSchema = Schemas.parse(schemaFile.getAbsolutePath(), locators, resolvers); } catch (IOException e) { logger.log(Level.WARNING, "Unable to parse schema: " + schemaFile.getAbsolutePath(), e); } if (ftSchema != null) { //respect the prefix (xs vs xsd) given by the underlying schema file if ( ftSchema.getSchemaForSchemaQNamePrefix() != null ) { schema.setSchemaForSchemaQNamePrefix(ftSchema.getSchemaForSchemaQNamePrefix()); } //add the contents of this schema to the schema being built //look up the complex type List contents = ftSchema.getContents(); //ensure that an element for the feature is present boolean hasElement = false; for (Iterator i = contents.iterator(); i.hasNext();) { XSDSchemaContent content = (XSDSchemaContent) i.next(); content.setElement(null); //check for import of gml, skip over since we already imported it if ( content instanceof XSDImport ) { XSDImport imprt = (XSDImport) content; if ( gmlNamespace.equals( imprt.getNamespace() ) ) { i.remove(); } } //check for duplicated elements and types if ( content instanceof XSDElementDeclaration ) { if ( contains( (XSDNamedComponent) content, schema.getElementDeclarations() ) ) { i.remove(); } } else if ( content instanceof XSDTypeDefinition ) { if ( contains( (XSDNamedComponent) content, schema.getTypeDefinitions() ) ) { i.remove(); } } // check for element if ( !hasElement && content instanceof XSDElementDeclaration ) { XSDElementDeclaration element = (XSDElementDeclaration) content; if ( name.equals( element.getName() ) && featureTypeMeta.getNamespace().getURI().equals( element.getTargetNamespace() ) ) { hasElement = true; } } } if ( !hasElement ) { //need to create an element declaration in the schema XSDElementDeclaration element = factory.createXSDElementDeclaration(); element.setName( featureTypeMeta.getName() ); element.setTargetNamespace( featureTypeMeta.getNamespace().getURI() ); synchronized (Schemas.class) { element.setSubstitutionGroupAffiliation(getFeatureElement()); } //find the type of the element List<XSDComplexTypeDefinition> candidates = new ArrayList<XSDComplexTypeDefinition>(); for ( Iterator t = ftSchema.getTypeDefinitions().iterator(); t.hasNext(); ) { XSDTypeDefinition type = (XSDTypeDefinition) t.next(); if ( type instanceof XSDComplexTypeDefinition ) { XSDTypeDefinition base = type.getBaseType(); while(base != null ) { if ( baseType.equals(base.getName()) && gmlNamespace.equals( base.getTargetNamespace() ) ) { candidates.add( (XSDComplexTypeDefinition) type ); break; } if ( base.equals( base.getBaseType() ) ) { break; } base = base.getBaseType(); } } } if ( candidates.size() != 1 ) { throw new IllegalStateException("Could not determine feature type for " + "generated element. Must specify explicitly in schema.xsd."); } element.setTypeDefinition( candidates.get(0)); schema.getContents().add(element); } schema.getContents().addAll(contents); schema.updateElement(); Schemas.dispose(ftSchema); return true; } } return false; } private XSDElementDeclaration getFeatureElement() { if(featureSubGroupElement == null) { synchronized (this) { if(featureSubGroupElement == null) { featureSubGroupElement = gmlSchema().resolveElementDeclaration(gmlNamespace, substitutionGroup); } } } return featureSubGroupElement; } private void buildSchemaContent(FeatureTypeInfo featureTypeMeta, XSDSchema schema, XSDFactory factory, String baseUrl) throws IOException { if (!findTypeInSchema(featureTypeMeta, schema, factory)) { // build the type manually XSDComplexTypeDefinition xsdComplexType = buildComplexSchemaContent(featureTypeMeta .getFeatureType(), schema, factory); XSDElementDeclaration element = factory.createXSDElementDeclaration(); element.setName(featureTypeMeta.getName()); element.setTargetNamespace(featureTypeMeta.getNamespace().getURI()); synchronized(Schemas.class) { // this call changes the global schemas too, need to be synchronized element.setSubstitutionGroupAffiliation(getFeatureElement()); } element.setTypeDefinition(xsdComplexType); schema.getContents().add(element); schema.updateElement(); } } /** * Construct an XSD type definition for a ComplexType. * * <p> * * A side-effect of calling this method is that the constructed type and any concrete nested * complex types are added to the schema. * * @param complexType * @param schema * @param factory * @return */ private XSDComplexTypeDefinition buildComplexSchemaContent(ComplexType complexType, XSDSchema schema, XSDFactory factory) { XSDComplexTypeDefinition xsdComplexType = factory.createXSDComplexTypeDefinition(); xsdComplexType.setName(complexType.getName().getLocalPart() + "Type"); xsdComplexType.setDerivationMethod(XSDDerivationMethod.EXTENSION_LITERAL); xsdComplexType.setBaseTypeDefinition(resolveTypeInSchema(schema, new NameImpl(gmlNamespace, baseType))); XSDModelGroup group = factory.createXSDModelGroup(); group.setCompositor(XSDCompositor.SEQUENCE_LITERAL); for (PropertyDescriptor pd : complexType.getDescriptors()) { if (pd instanceof AttributeDescriptor) { AttributeDescriptor attribute = (AttributeDescriptor) pd; if ( filterAttributeType( attribute ) ) { GMLInfo gml = getGMLConfig(wfs); if (gml == null || !gml.getOverrideGMLAttributes()) { continue; } } XSDElementDeclaration element = factory.createXSDElementDeclaration(); element.setName(attribute.getLocalName()); element.setNillable(attribute.isNillable()); Name typeName = attribute.getType().getName(); // skip if it's XS.AnyType. It's not added to XS.Profile, because // a lot of types extend XS.AnyType causing it to be the returned // binding.. I could make it so that it checks against all profiles // but would that slow things down? At the moment, it returns the // first matching one, so it doesn't go through all profiles. if (!(typeName.getLocalPart().equals(XS.ANYTYPE.getLocalPart()) && typeName .getNamespaceURI().equals(XS.NAMESPACE))) { if (attribute.getType() instanceof ComplexType) { // If non-simple complex property not in schema, recurse. // Note that abstract types will of course not be resolved; these must be // configured at global level, so they can be found by the // encoder. if (schema.resolveTypeDefinition(typeName.getNamespaceURI(), typeName .getLocalPart()) == null) { buildComplexSchemaContent((ComplexType) attribute.getType(), schema, factory); } } else { Class binding = attribute.getType().getBinding(); typeName = findTypeName(binding); if (typeName == null) { throw new NullPointerException("Could not find a type for property: " + attribute.getName() + " of type: " + binding.getName()); } } } //XSDTypeDefinition type = schema.resolveTypeDefinition(typeName.getNamespaceURI(), // typeName.getLocalPart()); XSDTypeDefinition type = resolveTypeInSchema(schema, typeName); element.setTypeDefinition(type); XSDParticle particle = factory.createXSDParticle(); particle.setMinOccurs(attribute.getMinOccurs()); particle.setMaxOccurs(attribute.getMaxOccurs()); particle.setContent(element); group.getContents().add(particle); } } XSDParticle particle = factory.createXSDParticle(); particle.setContent(group); xsdComplexType.setContent(particle); schema.getContents().add(xsdComplexType); return xsdComplexType; } XSDTypeDefinition resolveTypeInSchema(XSDSchema schema, Name typeName) { XSDTypeDefinition type = null; for (XSDTypeDefinition td : ((List<XSDTypeDefinition>)schema.getTypeDefinitions())) { if (typeName.getNamespaceURI().equals(td.getTargetNamespace()) && typeName.getLocalPart().equals(td.getName())) { type = td; break; } } if (type == null) { type = schema.resolveTypeDefinition(typeName.getNamespaceURI(), typeName.getLocalPart()); } return type; } boolean contains( XSDNamedComponent c, List l ) { boolean contains = false; for ( Iterator i = l.iterator(); !contains && i.hasNext(); ) { XSDNamedComponent e = (XSDNamedComponent) i.next(); if ( e.getName().equals( c.getName() ) ) { if ( e.getTargetNamespace() == null ) { contains = c.getTargetNamespace() == null; } else { contains = e.getTargetNamespace().equals( c.getTargetNamespace() ); } } } return contains; } Name findTypeName(Class binding) { for (Iterator p = profiles.iterator(); p.hasNext();) { Object profile = p.next(); Name name = null; if (profile instanceof TypeMappingProfile) { name = ((TypeMappingProfile)profile).name(binding); } else if (profile instanceof Schema){ Schema schema = (Schema) profile; for (Map.Entry<Name,AttributeType> e : schema.entrySet()) { AttributeType at = e.getValue(); if (at.getBinding() != null && at.getBinding().equals(binding)) { name = at.getName(); break; } } if (name == null) { for (AttributeType at : schema.values()) { if (binding.isAssignableFrom(at.getBinding())) { name = at.getName(); break; } } } } if (name != null) { return name; } } return null; } protected abstract XSDSchema gmlSchema(); protected abstract GMLInfo getGMLConfig(WFSInfo wfs); protected boolean filterAttributeType( AttributeDescriptor attribute ) { return "name".equals( attribute.getLocalName() ) || "description".equals( attribute.getLocalName()) || "boundedBy".equals( attribute.getLocalName()); } public static final class GML2 extends FeatureTypeSchemaBuilder { /** * Cached gml2 schema */ private static XSDSchema gml2Schema; public GML2(GeoServer gs) { super(gs); profiles.add(new GML2Profile()); gmlNamespace = org.geotools.gml2.GML.NAMESPACE; gmlSchemaLocation = "gml/2.1.2/feature.xsd"; baseType = "AbstractFeatureType"; substitutionGroup = "_Feature"; describeFeatureTypeParams = params("request", "DescribeFeatureType", "version", "1.0.0", "service", "WFS"); gmlPrefix = "gml"; xmlConfiguration = new GMLConfiguration(); } protected XSDSchema gmlSchema() { if (gml2Schema == null) { gml2Schema = xmlConfiguration.schema(); } return gml2Schema; } @Override protected GMLInfo getGMLConfig(WFSInfo wfs) { return wfs.getGML().get(WFSInfo.Version.V_10); } } public static class GML3 extends FeatureTypeSchemaBuilder { /** * Cached gml3 schema */ private static XSDSchema gml3Schema; public GML3(GeoServer gs) { super(gs); profiles.add(createTypeMappingProfile()); gmlNamespace = org.geotools.gml3.GML.NAMESPACE; gmlSchemaLocation = "gml/3.1.1/base/gml.xsd"; baseType = "AbstractFeatureType"; substitutionGroup = "_Feature"; describeFeatureTypeParams = params("request", "DescribeFeatureType", "version", "1.1.0", "service", "WFS"); gmlPrefix = "gml"; xmlConfiguration = new org.geotools.gml3.GMLConfiguration(); } Object createTypeMappingProfile() { return new GML3Profile(); } protected XSDSchema gmlSchema() { if (gml3Schema == null) { gml3Schema = createGmlSchema(); } return gml3Schema; } XSDSchema createGmlSchema() { return xmlConfiguration.schema(); } protected boolean filterAttributeType( AttributeDescriptor attribute ) { return super.filterAttributeType( attribute ) || "metaDataProperty".equals( attribute.getLocalName() ) || "location".equals( attribute.getLocalName() ); } @Override protected GMLInfo getGMLConfig(WFSInfo wfs) { return wfs.getGML().get(WFSInfo.Version.V_11); } } public static final class GML32 extends GML3 { public GML32(GeoServer gs) { super(gs); gmlNamespace = GML.NAMESPACE; gmlPrefix = "gml"; gmlSchemaLocation = "gml/3.2.1/gml.xsd"; baseType = "AbstractFeatureType"; substitutionGroup = "AbstractFeature"; describeFeatureTypeParams = params("request", "DescribeFeatureType", "version", "1.1.0", "service", "WFS", "outputFormat", "text/xml; subtype=gml/3.2"); xmlConfiguration = new org.geotools.gml3.v3_2.GMLConfiguration(); } @Override Object createTypeMappingProfile() { return GML.getInstance().getTypeMappingProfile(); } @Override XSDSchema createGmlSchema() { try { return GML.getInstance().getSchema(); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void importGMLSchema(XSDSchema schema, XSDFactory factory, String baseUrl) { XSDImport imprt; try { imprt = factory.createXSDImport(); imprt.setNamespace( WFS.getInstance().getSchema().getTargetNamespace() ); imprt.setSchemaLocation(ResponseUtils.buildSchemaURL(baseUrl, gmlSchemaLocation)); imprt.setResolvedSchema(WFS.getInstance().getSchema()); schema.getContents().add( imprt ); schema.getQNamePrefixToNamespaceMap().put("wfs", WFS.NAMESPACE); //imprt = Schemas.importSchema(schema, WFS.getInstance().getSchema()); ((XSDSchemaImpl)WFS.getInstance().getSchema()).imported(imprt); //((XSDSchemaImpl)schema).resolveSchema(WFS.NAMESPACE); //schema.getContents().add(imprt); } catch (IOException e) { throw new RuntimeException(e); } } protected boolean filterAttributeType( AttributeDescriptor attribute ) { return super.filterAttributeType( attribute ) || "descriptionReference".equals( attribute.getLocalName() ) || "identifier".equals( attribute.getLocalName() ); } /** * GML 3.2 responses require the WFS 2.0 schema to define WFS 2.0 FeatureCollection, which * is used as the root element. * * @see org.geoserver.wfs.xml.FeatureTypeSchemaBuilder#getWfsSchema() */ @Override protected XSDSchema getWfsSchema() { try { return WFS.getInstance().getSchema(); } catch (IOException e) { throw new RuntimeException(e); } } } }