/*
* 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.storedquery;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import net.opengis.fes._2.AbstractQueryExpressionType;
import net.opengis.wfs._2.GetFeatureType;
import net.opengis.wfs._2.ParameterExpressionType;
import net.opengis.wfs._2.ParameterType;
import net.opengis.wfs._2.QueryExpressionTextType;
import net.opengis.wfs._2.StoredQueryDescriptionType;
import net.opengis.wfs._2.StoredQueryType;
import net.opengis.wfs._2.Title;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import vcs.citydb.wfs.config.Constants;
import vcs.citydb.wfs.exception.WFSException;
import vcs.citydb.wfs.exception.WFSExceptionCode;
import vcs.citydb.wfs.xml.NamespaceFilter;
public class StoredQuery {
public final static String DEFAULT_LANGUAGE = "urn:ogc:def:queryLanguage:OGC-WFS::WFS_QueryExpression";
private final StoredQueryDescriptionType description;
private final StoredQueryManager manager;
private final NamespaceFilter namespaceFilter;
StoredQuery(StoredQueryDescriptionType description, NamespaceFilter namespaceFilter, StoredQueryManager manager) {
this.description = description;
this.namespaceFilter = namespaceFilter;
this.manager = manager;
}
public String getId() {
return description.getId();
}
public List<Title> getTitle() {
return description.getTitle();
}
public NamespaceFilter getNamespaceFilter() {
return namespaceFilter;
}
public List<QName> getReturnFeatureTypeNames() {
Set<QName> featureTypeNames = new HashSet<QName>();
for (QueryExpressionTextType queryExpression : description.getQueryExpressionText())
featureTypeNames.addAll(queryExpression.getReturnFeatureTypes());
return new ArrayList<QName>(featureTypeNames);
}
public StoredQueryDescriptionType getStoredQueryDescription() {
return description;
}
public void validate(String handle) throws WFSException {
// TODO: implement
}
public List<AbstractQueryExpressionType> compile(StoredQueryType storedQuery, NamespaceFilter namespaceFilter) throws WFSException {
if (description.getQueryExpressionText().isEmpty())
throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "The stored query '" + getId() + "' does not specify any query expression texts.", storedQuery.getHandle());
List<AbstractQueryExpressionType> queries = new ArrayList<AbstractQueryExpressionType>();
try {
// build parameter map
HashMap<String, String> parameterMap = new HashMap<String, String>();
for (ParameterType parameter : storedQuery.getParameter()) {
// check whether parameter is declared
ParameterExpressionType parameterExpression = null;
for (ParameterExpressionType tmp : description.getParameter()) {
if (tmp.getName().equals(parameter.getName())) {
parameterExpression = tmp;
break;
}
}
if (parameterExpression == null)
throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The paramter '" + parameter.getName() + "' is not declared for the stored query '" + storedQuery.getId() + "'.", storedQuery.getHandle());
// convert parameter value to string
StringBuilder value = new StringBuilder();
for (Object content : parameter.getContent())
value.append(toString(content));
// TODO: we should do a further type mismatch check
parameterMap.put(parameter.getName(), value.toString());
}
// check for missing parameters
if (description.getParameter().size() != parameterMap.size()) {
for (ParameterExpressionType parameter : description.getParameter()) {
if (!parameterMap.containsKey(parameter.getName()))
throw new WFSException(WFSExceptionCode.MISSING_PARAMETER_VALUE, "The parameter '" + parameter.getName() + "' of the stored query lacks a value.", storedQuery.getHandle());
}
}
// iterate over query expressions and replace tokens with parameter values
for (QueryExpressionTextType expressionText : description.getQueryExpressionText()) {
if (expressionText.getContent().isEmpty())
throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "The stored query '" + storedQuery.getId() + "' does not specify any query elements.", storedQuery.getHandle());
StringBuilder template = new StringBuilder();
// we need to embrace the query expression with a GetFeature request since
// the expression text can contain multiple query elements.
template.append('<').append(Constants.WFS_NAMESPACE_PREFIX).append(':').append("GetFeature xmlns:").append(Constants.WFS_NAMESPACE_PREFIX).append("=\"").append(Constants.WFS_NAMESPACE_URI).append("\">");
// get string representation of query expression
for (Object content : expressionText.getContent())
template.append(toString(content));
// replace tokens
for (ParameterType parameter : storedQuery.getParameter()) {
String name = parameter.getName();
String token = "${" + name + "}";
int index = template.indexOf(token);
while (index > 0) {
template.replace(index, index + token.length(), parameterMap.get(name));
index = template.indexOf(token, index + token.length());
}
}
// close GetFeature
template.append("</").append(Constants.WFS_NAMESPACE_PREFIX).append(':').append("GetFeature>");
// unmarshal to GetFeature request
Object result = unmarshal(template.toString());
if (!(result instanceof JAXBElement<?>))
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to compile the stored query '" + storedQuery.getId() + "' after token replacement.", storedQuery.getHandle());
JAXBElement<?> jaxbElement = (JAXBElement<?>)result;
if (!(jaxbElement.getValue() instanceof GetFeatureType))
throw new WFSException(WFSExceptionCode.OPERATION_PARSING_FAILED, "The query expression text of the stored query '" + storedQuery.getId() + "' cannot be compiled to a valid set of query elements.", storedQuery.getHandle());
GetFeatureType getFeature = (GetFeatureType)jaxbElement.getValue();
for (JAXBElement<? extends AbstractQueryExpressionType> queryElement: getFeature.getAbstractQueryExpression())
queries.add(queryElement.getValue());
// finally propagate namespace bindings of the stored query
Iterator<String> iter = this.namespaceFilter.getPrefixes();
while (iter.hasNext()) {
String prefix = iter.next();
namespaceFilter.startPrefixMapping(prefix, this.namespaceFilter.getNamespaceURI(prefix));
}
}
} catch (JAXBException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to compile the stored query '" + storedQuery.getId() + "'.", storedQuery.getHandle(), e);
} catch (TransformerFactoryConfigurationError e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to compile the stored query '" + storedQuery.getId() + "'.", storedQuery.getHandle(), e);
} catch (TransformerException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to compile the stored query '" + storedQuery.getId() + "'.", storedQuery.getHandle(), e);
} catch (SAXException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to compile the stored query '" + storedQuery.getId() + "'.", storedQuery.getHandle(), e);
}
return queries;
}
private String toString(Object object) throws JAXBException, TransformerFactoryConfigurationError, TransformerException {
if (object instanceof Element)
return marshal((Element)object);
else if (object instanceof JAXBElement<?>)
return marshal((JAXBElement<?>)object);
else
return object.toString().trim();
}
private String marshal(Element element) throws TransformerFactoryConfigurationError, TransformerException {
Transformer transformer = manager.getTransformerFactory().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(element);
transformer.transform(source, result);
return result.getWriter().toString();
}
private String marshal(JAXBElement<?> element) throws JAXBException {
StringWriter writer = new StringWriter();
Marshaller marshaller = manager.getJAXBBuilder().getJAXBContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
marshaller.marshal(element, writer);
return writer.toString();
}
private Object unmarshal(String object) throws JAXBException {
StringReader reader = new StringReader(object);
Unmarshaller unmarshaller = manager.getJAXBBuilder().getJAXBContext().createUnmarshaller();
return unmarshaller.unmarshal(reader);
}
}