/*
* 3D City Database Web Feature Service
* http://www.3dcitydb.org/
*
* Copyright 2014 - 2016
* virtualcitySYSTEMS GmbH
* Tauentzienstrasse 7b/c
* 10789 Berlin, Germany
* http://www.virtualcitysystems.de/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vcs.citydb.wfs.operation;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import org.citydb.api.registry.ObjectRegistry;
import org.citygml4j.model.citygml.CityGMLClass;
import org.citygml4j.model.module.Module;
import org.citygml4j.model.module.Modules;
import org.citygml4j.model.module.citygml.AppearanceModule;
import org.citygml4j.model.module.citygml.CityGMLModule;
import org.citygml4j.model.module.citygml.CityGMLVersion;
import org.citygml4j.model.module.citygml.GenericsModule;
import org.citygml4j.xml.schema.ElementDecl;
import org.citygml4j.xml.schema.Schema;
import org.citygml4j.xml.schema.SchemaHandler;
import org.citygml4j.xml.schema.SchemaWalker;
import vcs.citydb.wfs.config.WFSConfig;
import vcs.citydb.wfs.config.feature.FeatureType;
import vcs.citydb.wfs.exception.WFSException;
import vcs.citydb.wfs.exception.WFSExceptionCode;
import com.sun.xml.xsom.XSAttributeUse;
import com.sun.xml.xsom.XSElementDecl;
public class FeatureTypeHandler {
private final SchemaHandler schemaHandler;
private final WFSConfig wfsConfig;
public FeatureTypeHandler() {
schemaHandler = (SchemaHandler)ObjectRegistry.getInstance().lookup(SchemaHandler.class.getName());
wfsConfig = (WFSConfig)ObjectRegistry.getInstance().lookup(WFSConfig.class.getName());
}
public Set<QName> getFeatureTypeNames(Collection<QName> featureTypeNames, boolean canBeEmpty, String handle) throws WFSException {
Set<QName> result = new HashSet<QName>();
for (QName featureTypeName : featureTypeNames) {
if (featureTypeName == null)
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Failed to interpret feature type name as XML qualified name.", handle);
validateFeatureTypeName(featureTypeName, handle);
// check whether the requested feature type is advertised
if (!wfsConfig.getFeatureTypes().getAdvertisedFeatureTypes().contains(featureTypeName))
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The requested feature type '" + featureTypeName.toString() + "' is not advertised.", handle);
result.add(featureTypeName);
}
// check whether the query must contain at least one feature type name
if (!canBeEmpty && result.isEmpty())
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The operation requires at least one feature type name to be provided.", handle);
// check whether all feature types share the same CityGML version
CityGMLVersion version = null;
for (QName name : result) {
CityGMLVersion featureVersion = CityGMLVersion.fromCityGMLModule(Modules.getCityGMLModule(name.getNamespaceURI()));
if (version == null) {
version = featureVersion;
continue;
} else if (featureVersion != version)
throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Mixing feature types from different CityGML versions is not supported.", handle);
}
return result;
}
public Set<QName> getFeatureTypeNames(Collection<String> featureTypeNames, NamespaceContext namespaceContext, boolean canBeEmpty, String handle) throws WFSException {
Set<QName> result = new HashSet<QName>();
for (String featureTypeName : featureTypeNames) {
String candidate = featureTypeName;
boolean isSchemaElementFunction = false;
// TODO: although parsing of the schema-element() function is already
// implemented, the function itself is not supported in the following.
if (featureTypeName.startsWith("schema-element")) {
Pattern pattern = Pattern.compile("schema-element\\((.+)\\)$");
Matcher matcher = pattern.matcher(featureTypeName);
if (matcher.matches()) {
candidate = matcher.group(1);
isSchemaElementFunction = true;
} else
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Invalid schema-element() function: '" + featureTypeName + "'.", handle);
}
String namespacePrefix = null;
String namespaceURI = null;
String localPart = null;
String[] parts = candidate.split(":");
if (parts.length == 1) {
namespacePrefix = XMLConstants.DEFAULT_NS_PREFIX;
localPart = parts[0];
} else if (parts.length == 2) {
namespacePrefix = parts[0];
localPart = parts[1];
} else
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Invalid feature type name: '" + candidate + "'.", handle);
namespaceURI = namespaceContext.getNamespaceURI(namespacePrefix);
if (namespaceURI == null)
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The feature type name '" + candidate + "' lacks a namespace declaration.", handle);
QName qName = new QName(namespaceURI, localPart);
if (!isSchemaElementFunction)
result.add(qName);
else
result.addAll(resolveSchemaElementFunction(qName, handle));
}
return getFeatureTypeNames(result, canBeEmpty, handle);
}
public boolean isPropertyElementOf(QName featureTypeName, final QName propertyName, String handle) throws WFSException {
ElementDecl element = getElementDecl(featureTypeName, handle);
SchemaWalker schemaWalker = new SchemaWalker() {
@Override
public void elementDecl(XSElementDecl child) {
if (child.getName().equals(propertyName.getLocalPart()) &&
child.getTargetNamespace().equals(propertyName.getNamespaceURI())) {
setShouldWalk(false);
}
}
@Override
public void attributeUse(XSAttributeUse use) {
// avoid visiting attribute use
}
};
element.getXSElementDecl().getType().visit(schemaWalker);
boolean isPropertyElement = !schemaWalker.shouldWalk();
// TOOO: check for generic attributes and appearance attributes
if (!isPropertyElement) {
String namespaceURI = propertyName.getNamespaceURI();
isPropertyElement = namespaceURI.equals(GenericsModule.v1_0_0.getNamespaceURI()) ||
namespaceURI.equals(AppearanceModule.v1_0_0.getNamespaceURI());
}
return isPropertyElement;
}
public CityGMLClass getCityGMLClass(QName featureTypeName) {
Module module = Modules.getModule(featureTypeName.getNamespaceURI());
if (module instanceof CityGMLModule) {
CityGMLModule cityGMLModule = (CityGMLModule)module;
return CityGMLClass.fromModelClass(cityGMLModule.getFeatureElementClass(featureTypeName.getLocalPart()));
}
return CityGMLClass.UNDEFINED;
}
private ElementDecl getElementDecl(QName featureTypeName, String handle) throws WFSException {
Schema schema = schemaHandler.getSchema(featureTypeName.getNamespaceURI());
if (schema == null)
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The feature type '" + featureTypeName.toString() + "' is not part of CityGML.", handle);
// TODO: in CityGML 1.0, app:Appearance is not declared as global element and hence
// would not fulfill the following test. However, appearances are not supported so far anyways...
ElementDecl element = schema.getGlobalElementDecl(featureTypeName.getLocalPart());
if (element == null)
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The feature type '" + featureTypeName.getLocalPart() + "' is not defined in the associated namespace '" + featureTypeName.getNamespaceURI() + "'.", handle);
return element;
}
private boolean validateFeatureTypeName(QName featureTypeName, String handle) throws WFSException {
return getElementDecl(featureTypeName, handle) != null;
}
private Set<QName> resolveSchemaElementFunction(QName featureTypeName, String handle) throws WFSException {
CityGMLVersion version = CityGMLVersion.fromCityGMLModule(Modules.getCityGMLModule(featureTypeName.getNamespaceURI()));
if (!wfsConfig.getFeatureTypes().getVersions().contains(version))
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Feature types from CityGML version '" + version + "' are not advertised.", handle);
if (featureTypeName.getLocalPart().equals("_CityObject")) {
Set<QName> types = new HashSet<QName>();
for (FeatureType type : wfsConfig.getFeatureTypes().getFeatureTypes())
types.add(type.getQName(version));
return types;
}
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Only the abstract super type '_CityObject' may be used as parameter for the schema-element() function.", handle);
}
}