Package org.geoserver.wfs.xml

Source Code of org.geoserver.wfs.xml.FeatureTypeSchemaBuilder

/* 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.*;

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.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.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.WFSInfo;
import org.geotools.gml2.GMLConfiguration;
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.ComplexType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;

/**
* 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 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 XSDSchema build(FeatureTypeInfo featureTypeInfo, String baseUrl)
        throws IOException {
        return build(new FeatureTypeInfo[] { featureTypeInfo }, baseUrl);
    }

    public XSDSchema build(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 1 xsd:include if schema location is specified
            try {
                FeatureType featureType = featureTypeInfos[0].getFeatureType();
                Object schemaUri = featureType.getUserData().get("schemaURI");
                if (schemaUri != null) {
                    // should always be a string.. set in AppSchemaDataAccessConfigurator
                    assert schemaUri instanceof String;
                    // schema is supplied by the user.. just include the top level schema instead of
                    // building the type
                    if (!findTypeInSchema(featureTypeInfos[0], schema, factory)) {
                        addInclude(schema, factory, targetNamespace, (String) schemaUri);
                    }
                    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
            XSDImport imprt = factory.createXSDImport();
            imprt.setNamespace(gmlNamespace);

            imprt.setSchemaLocation(ResponseUtils.buildSchemaURL(baseUrl, gmlSchemaLocation));

            XSDSchema gmlSchema = gmlSchema();
            imprt.setResolvedSchema(gmlSchema);

            schema.getContents().add(imprt);
            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
            ArrayList<String> importedNamespaces = new ArrayList<String>(
                    ns2featureTypeInfos.size() + 1);
            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();
                String namespaceURI;
                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) {
                        // should always be a string.. set in AppSchemaDataAccessConfigurator
                        assert schemaUri instanceof String;
                        // schema is supplied by the user.. just import the top level schema instead of
                        // building
                        // the type
                        namespaceURI = featureType.getName().getNamespaceURI();

                        addImport(schema, factory, namespaceURI,
                                (String) schemaUri, importedNamespaces);

                        // ensure there's only 1 import per namespace
                        importedNamespaces.add(namespaceURI);

                    } 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);
                }
            }
        }

        return schema;
    }

    /**
     * Add include statement to schema.
     *
     * @param schema
     *            Output schema
     * @param factory
     *            XSD factory used to produce schema
     * @param nsURI
     *            Name space URI
     * @param schemaLocation
     *            The schema location to be included
     */
    private void addInclude(XSDSchema schema, XSDFactory factory, String nsURI, String schemaLocation) {
        XSDInclude xsdInclude = factory.createXSDInclude();
        xsdInclude.setSchemaLocation(schemaLocation);
        schema.getContents().add(xsdInclude);       
        schema.updateElement();
    }

    /**
     * Add import statement to schema.
     *
     * @param schema
     *            Output schema
     * @param factory
     *            XSD factory used to produce schema
     * @param nsURI
     *            Import name space
     * @param schemaLocations
     *            The schema to be imported
     * @param importedNamespaces
     *            List of already imported name spaces
     */
    private void addImport(XSDSchema schema, XSDFactory factory, String nsURI, String schemaURI,
            List<String> importedNamespaces) {
        if (!importedNamespaces.contains(nsURI)) {
            XSDImport xsdImport = factory.createXSDImport();
            xsdImport.setNamespace(nsURI);
            xsdImport.setSchemaLocation((String) schemaURI);
            schema.getContents().add(xsdImport);
            importedNamespaces.add(nsURI);
        }
    }

    /**
     * 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 featureTypeInfos = catalog.getFeatureTypes();

        for (Iterator i = featureTypeInfos.iterator(); i.hasNext();) {
            FeatureTypeInfo meta = (FeatureTypeInfo) i.next();
           
            // don't build schemas for disabled feature types
            if(!meta.enabled())
                continue;

            //build the schema for the types in the single namespace
            XSDSchema schema = build(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 t = schema.getTypeDefinitions().iterator(); t.hasNext();) {
                wfsSchema.getTypeDefinitions().add(t.next());
            }

            for (Iterator e = schema.getElementDeclarations().iterator(); e.hasNext();) {
                wfsSchema.getElementDeclarations().add(e.next());
            }
           
            // add secondary namespaces from catalog
            for (Map.Entry entry : (Set<Map.Entry>) 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) {
            //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() );
                    element.setSubstitutionGroupAffiliation(
                        schema.resolveElementDeclaration(gmlNamespace, substitutionGroup));
                   
                    //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();

                return true;
            }
        }
        return false;
    }

    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.setSubstitutionGroupAffiliation(schema.resolveElementDeclaration(gmlNamespace,
                    substitutionGroup));
            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(schema.resolveComplexTypeDefinition(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 ) ) {
                    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());
                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;
    }

    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();) {
            TypeMappingProfile profile = (TypeMappingProfile) p.next();
            Name name = profile.name(binding);

            if (name != null) {
                return name;
            }
        }

        return null;
    }

    protected abstract XSDSchema gmlSchema();

    protected boolean filterAttributeType( AttributeDescriptor attribute ) {
        return "name".equals( attribute.getName() )
            || "description".equals( attribute.getName())
            || "boundedBy".equals( attribute.getName());
    }
   
    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;
        }
    }

    public static final class GML3 extends FeatureTypeSchemaBuilder {
        /**
         * Cached gml3 schema
         */
        private static XSDSchema gml3Schema;

        public GML3(GeoServer gs) {
            super(gs);

            profiles.add(new GML3Profile());

            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();
        }

        protected XSDSchema gmlSchema() {
            if (gml3Schema == null) {
                gml3Schema = xmlConfiguration.schema();
            }

            return gml3Schema;
        }
       
        protected boolean filterAttributeType( AttributeDescriptor attribute ) {
            return super.filterAttributeType( attribute ) ||
                "metaDataProperty".equals( attribute.getName() ) ||
                "location".equals( attribute.getName() );
        }
    }
}
TOP

Related Classes of org.geoserver.wfs.xml.FeatureTypeSchemaBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.