/* 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.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
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.XSDModelGroup;
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.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.ResponseUtils;
import org.geotools.gml2.GML;
import org.geotools.util.logging.Logging;
import org.geotools.wfs.WFS;
import org.geotools.xml.SchemaIndex;
import org.geotools.xml.Schemas;
import org.geotools.xml.XSD;
import org.geotools.xml.impl.SchemaIndexImpl;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.Schema;
/**
* XSD for an application schema of a geoserver feature type.
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class ApplicationSchemaXSD extends XSD {
static Logger LOGGER = Logging.getLogger( "org.geoserver.wfs" );
/**
* namespace for the app schema
*/
NamespaceInfo ns;
/**
* the catalog
*/
Catalog catalog;
/**
* base url used for schemaLocation
*/
String baseURL;
/**
* wfs schema
*/
WFS wfs;
/**
* list of types to be included in the app schema.
*/
Collection<FeatureTypeInfo> types;
/**
* type mapping profile
*/
TypeMappingProfile typeMappingProfile;
public ApplicationSchemaXSD( NamespaceInfo ns, Catalog catalog, String baseURL, WFS wfs ) {
this( ns, catalog, baseURL, wfs, Collections.EMPTY_LIST );
}
public ApplicationSchemaXSD( NamespaceInfo ns, Catalog catalog, String baseURL, WFS wfs, Collection<FeatureTypeInfo> types ) {
this.ns = ns;
this.catalog = catalog;
this.baseURL = baseURL;
this.wfs = wfs;
this.types = types;
if ( this.types == null ) {
types = Collections.EMPTY_LIST;
}
if ( this.ns == null ) {
//ns set to null, if all given types are from the same namespace, us that one
if ( !types.isEmpty() ) {
Iterator<FeatureTypeInfo> i = types.iterator();
NamespaceInfo namespace = i.next().getNamespace();
while( i.hasNext() ) {
FeatureTypeInfo type = i.next();
if ( !namespace.equals( type.getNamespace() ) ) {
namespace = null;
break;
}
}
if ( namespace != null ) {
this.ns = namespace;
}
}
}
//set up type mapping profiles
Set<Schema> profiles = new LinkedHashSet<Schema>();
for ( XSD xsd : wfs.getAllDependencies() ) {
profiles.add( xsd.getTypeMappingProfile() );
}
typeMappingProfile = new TypeMappingProfile( profiles );
}
@Override
protected void addDependencies(Set dependencies) {
dependencies.add( wfs );
}
@Override
public String getNamespaceURI() {
//namespace will be null in case where types from different namespaces are
//included in this schema
if ( ns != null ) {
return ns.getURI();
}
return null;
}
@Override
public String getSchemaLocation() {
String schemaLocation = ResponseUtils.appendQueryString( ResponseUtils.appendPath( baseURL, "wfs" ),
"request=DescribeFeatureType&service=WFS&version=" + wfs.getVersion() );
if ( types.isEmpty() ) {
schemaLocation = ResponseUtils.appendQueryString( schemaLocation, "namespace=" + ns.getURI() );
}
else {
StringBuffer sb = new StringBuffer( "typeName=");
for ( FeatureTypeInfo type : types ) {
sb.append( type.getPrefixedName() ).append( ",");
}
sb.setLength( sb.length() - 1);
schemaLocation = ResponseUtils.appendQueryString( schemaLocation, sb.toString() );
}
return schemaLocation;
}
@Override
protected XSDSchema buildSchema() 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));
if ( ns == null ) {
//there must be types from different namespaces included, build a schema
// with imports
if ( types.isEmpty() ) {
//build for all namespaces
buildSchemaImports( catalog.getNamespaces(), schema, factory );
}
else {
Set<NamespaceInfo> namespaces = new HashSet<NamespaceInfo>();
for ( FeatureTypeInfo type : types ) {
namespaces.add( type.getNamespace() );
}
buildSchemaImports( namespaces, schema, factory );
}
}
else {
schema.setTargetNamespace(ns.getURI());
schema.getQNamePrefixToNamespaceMap().put(ns.getPrefix(), ns.getURI());
schema.getQNamePrefixToNamespaceMap().put("wfs", WFS.NAMESPACE);
schema.getQNamePrefixToNamespaceMap().put("gml", GML.NAMESPACE);
//import wfs schema
Schemas.importSchema( schema ,wfs.getSchema() );
schema.resolveElementDeclaration( WFS.NAMESPACE, "FeatureCollection" );
/*
XSDImport imprt = factory.createXSDImport();
imprt.setNamespace(WFS.NAMESPACE);
String location = ResponseUtils.appendPath( baseURL, "schemas/wfs/");
location += wfs.getVersion() + "/";
location += new File( new URL(wfs.getSchemaLocation()).getFile() ).getName();
imprt.setNamespace(location);
imprt.setResolvedSchema(wfs.getSchema());
schema.getContents().add(imprt);
*/
Collection<FeatureTypeInfo> featureTypes = types;
if ( featureTypes == null || featureTypes.isEmpty() ) {
//grab all from catalog
featureTypes = catalog.getFeatureTypesByNamespace(ns);
}
SchemaIndexImpl index = new SchemaIndexImpl( new XSDSchema[]{ schema });
//all types in same namespace, write out the schema
for (FeatureTypeInfo featureType : featureTypes ) {
buildSchemaContent(featureType, schema, factory, index );
}
}
return schema;
}
void buildSchemaImports(Collection<NamespaceInfo> namespaces, XSDSchema schema, XSDFactory factory) {
Map<String, String> params = params("service", "wfs",
"request", "DescribeFeatureType",
"version", wfs.getVersion());
// String schemaLocation = ResponseUtils.appendQueryString(ResponseUtils.appendPath( baseURL, "wfs"),
// "service=wfs&request=DescribeFeatureType&version=" + wfs.getVersion() + "&namespace=");
for ( NamespaceInfo namespace : namespaces ) {
XSDImport imprt = factory.createXSDImport();
imprt.setNamespace(namespace.getURI());
params.put("namespace", namespace.getPrefix());
imprt.setSchemaLocation(buildURL(baseURL, "wfs", params, URLType.SERVICE));
schema.getContents().add(imprt);
}
}
void buildSchemaContent(FeatureTypeInfo featureTypeInfo,
XSDSchema schema, XSDFactory factory, SchemaIndex index) throws IOException {
//look if the schema for the type is already defined
String prefix = featureTypeInfo.getNamespace().getPrefix();
String store = featureTypeInfo.getStore().getName();
String name = featureTypeInfo.getName();
File schemaFile = null;
try {
schemaFile = catalog.getResourceLoader().find("workspaces" + "/" + prefix + "/" + store +
"/" + name + "/schema.xsd");
} catch (IOException e1) {
}
if (schemaFile != null) {
//schema file found, parse it and lookup the complex type
List locators = new ArrayList();
for ( XSD xsd : wfs.getAllDependencies() ) {
locators.add( xsd.createSchemaLocator() );
}
XSDSchema ftSchema = null;
try {
ftSchema = Schemas.parse(schemaFile.getAbsolutePath(), locators, null);
} catch (IOException e) {
LOGGER.log(Level.WARNING,
"Unable to parse schema: " + schemaFile.getAbsolutePath(), e);
}
if (ftSchema != null) {
//add the contents of this schema to the schema being built
//look up the complex type
List contents = ftSchema.getContents();
for (Iterator i = contents.iterator(); i.hasNext();) {
XSDSchemaContent content = (XSDSchemaContent) i.next();
content.setElement(null);
}
schema.getContents().addAll(contents);
schema.updateElement();
return;
}
}
//build the type manually
SimpleFeatureType featureType = (SimpleFeatureType) featureTypeInfo.getFeatureType();
XSDComplexTypeDefinition complexType = factory.createXSDComplexTypeDefinition();
complexType.setName(name+ "Type");
complexType.setDerivationMethod(XSDDerivationMethod.EXTENSION_LITERAL);
complexType.setBaseTypeDefinition(
schema.resolveComplexTypeDefinition(GML.NAMESPACE, "AbstractFeatureType"));
XSDModelGroup group = factory.createXSDModelGroup();
group.setCompositor(XSDCompositor.SEQUENCE_LITERAL);
List attributes = featureType.getAttributeDescriptors();
for (int i = 0; i < attributes.size(); i++) {
AttributeDescriptor attribute = (AttributeDescriptor) attributes.get(i);
if ( filterAttributeType( attribute ) ) {
continue;
}
XSDElementDeclaration element = factory.createXSDElementDeclaration();
element.setName(attribute.getLocalName());
element.setNillable(attribute.isNillable());
Class binding = attribute.getType().getBinding();
Name typeName = findTypeName(binding);
if (typeName == null) {
throw new NullPointerException("Could not find a type for property: "
+ attribute.getName() + " of type: " + binding.getName());
}
XSDTypeDefinition type = index.getTypeDefinition( new QName( typeName.getNamespaceURI(), typeName.getLocalPart() ) );
if ( type == null ) {
throw new IllegalStateException( "Could not find type: " + typeName );
}
//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);
complexType.setContent(particle);
schema.getContents().add(complexType);
XSDElementDeclaration element = factory.createXSDElementDeclaration();
element.setName(name);
element.setSubstitutionGroupAffiliation(schema.resolveElementDeclaration(GML.NAMESPACE,
"_Feature"));
element.setTypeDefinition(complexType);
schema.getContents().add(element);
schema.updateElement();
}
boolean filterAttributeType( AttributeDescriptor attribute ) {
return "name".equals( attribute.getName().getLocalPart() )
|| "description".equals( attribute.getName().getLocalPart() )
|| "boundedBy".equals( attribute.getName().getLocalPart());
}
Name findTypeName(Class binding) {
return typeMappingProfile.name(binding);
}
}