package de.ifgi.lod4wfs.factory;
/**
* @author Jim Jones
* @description Provides all standard WFS functions (GetCapabilities, DescribeFeatureType and GetFeature) for LOD data sources.
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import de.ifgi.lod4wfs.core.GlobalSettings;
import de.ifgi.lod4wfs.core.Triple;
import de.ifgi.lod4wfs.core.Utils;
import de.ifgi.lod4wfs.core.WFSFeature;
import de.ifgi.lod4wfs.infrastructure.JenaConnector;
public class AdapterLOD4WFS {
private static AdapterLOD4WFS instance;
private FactorySDAFeatures factorySDA;
private FactoryFDAFeatures factoryFDA;
private static JenaConnector jn;
private static Logger logger = Logger.getLogger("LOD4WFS-Adapter");
public AdapterLOD4WFS(){
factorySDA = new FactorySDAFeatures();
factoryFDA = new FactoryFDAFeatures();
jn = new JenaConnector();
}
public static AdapterLOD4WFS getInstance() {
if (instance == null) {
instance = new AdapterLOD4WFS();
}
return instance;
}
public String describeFeatureType(WFSFeature feature){
String featureName = FactoryWFS.getInstance().getLoadedModelFeature().expandPrefix((feature.getName()));
String describeFeatureTypeResponse = new String();
ArrayList<Triple> predicates = new ArrayList<Triple>();
if(feature.isFDAFeature()){
for (int i = 0; i < feature.getTableOfContents().size(); i++) {
Triple triple = new Triple();
triple.setPredicate(feature.getTableOfContents().get(i).getField());
triple.setObjectDataType(feature.getTableOfContents().get(i).getFieldType());
predicates.add(triple);
}
} else {
predicates = factorySDA.getPredicatesSDAFeatures(featureName);
Triple triple = new Triple();
triple.setPredicate(GlobalSettings.getGeometryPredicate().replace("<", "").replace(">", ""));
predicates.add(triple);
}
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse("wfs/DescribeFeature_100.xml");
logger.info("Creating DescribeFeatureType XML document for [" + feature.getName() + "] ...");
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList myNodeList = (NodeList) xpath.compile("//extension/sequence/text()").evaluate(document, XPathConstants.NODESET);
String layerPrefix = FactoryWFS.getInstance().getLoadedModelFeature().expandPrefix((feature.getName()));
layerPrefix = layerPrefix.substring(0,layerPrefix.indexOf(":") + 1);
Element requestElement = document.getDocumentElement();
requestElement.setAttribute("targetNamespace", FactoryWFS.getInstance().getLoadedModelFeature().expandPrefix(layerPrefix));
for (Map.Entry<String, String> entry : FactoryWFS.getInstance().getLoadedModelFeature().getNsPrefixMap().entrySet())
{
requestElement.setAttribute("xmlns:" + entry.getKey(), entry.getValue());
}
for (int i = 0; i < predicates.size(); i++) {
String predicateWithoutPrefix = new String();
predicateWithoutPrefix = this.removePredicateURL(predicates.get(i).getPredicate());
Element sequence = document.createElement("xsd:element");
sequence.setAttribute("maxOccurs","1");
sequence.setAttribute("minOccurs","0");
sequence.setAttribute("name", predicateWithoutPrefix);
sequence.setAttribute("nillable","true");
/**
* Checks if predicate is the geometry predicate indicated in the settings file.
*/
if(predicates.get(i).getPredicate().equals(GlobalSettings.getGeometryPredicate().replaceAll("<", "").replace(">", ""))){
String featureType = new String();
featureType = factorySDA.getFeatureType(featureName);
if(featureType.equals("gml:MultiPolygon") || featureType.equals("gml:Polygon")){
sequence.setAttribute("type","gml:MultiPolygonPropertyType");
}
if(featureType.equals("gml:LineString")){
sequence.setAttribute("type","gml:MultiLineStringPropertyType");
}
if(featureType.equals("gml:Point") || featureType.equals("gml:MultiPoint") ){
sequence.setAttribute("type","gml:MultiPointPropertyType");
}
} else if(feature.isFDAFeature() && (predicates.get(i).getPredicate().equals(feature.getGeometryVariable()))){
//TODO: create function to identify geometry type!!!
sequence.setAttribute("type","gml:MultiPointPropertyType");
} else {
sequence.setAttribute("type",predicates.get(i).getObjectDataType());
}
myNodeList.item(0).getParentNode().insertBefore(sequence, myNodeList.item(0));
}
describeFeatureTypeResponse = Utils.printXMLDocument(document);
describeFeatureTypeResponse = describeFeatureTypeResponse.replace("PARAM_NAME", feature.getName());
describeFeatureTypeResponse = describeFeatureTypeResponse.replace("PARAM_TYPE", feature.getName());
describeFeatureTypeResponse = describeFeatureTypeResponse.replace("PARAM_SERVER_PORT", Integer.toString(GlobalSettings.getDefaultPort()));
describeFeatureTypeResponse = describeFeatureTypeResponse.replace("PARAM_SERVICE", GlobalSettings.getDefaultServiceName());
describeFeatureTypeResponse = describeFeatureTypeResponse.replace("PARAM_SERVER", java.net.InetAddress.getLocalHost().getHostName());
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
}
return describeFeatureTypeResponse;
}
/**
* @see OGC Specification for WFS http://www.opengeospatial.org/standards/wfs
* @param geographic feature to be retrieved.
* @return XML Document containing the WFS GetFeature response with all geometries of a given feature together with their attribute table.
*/
public String getFeature(WFSFeature feature) {
String getFeatureResponse = new String();
String layerPrefix = new String();
String geometryType = "";
ArrayList<Triple> predicates = new ArrayList<Triple>();
ResultSet rs;
if(feature.isFDAFeature()){
for (int i = 0; i < feature.getTableOfContents().size(); i++) {
Triple triple = new Triple();
triple.setPredicate(feature.getTableOfContents().get(i).getField());
triple.setObjectDataType(feature.getTableOfContents().get(i).getFieldType());
predicates.add(triple);
}
rs = jn.executeQuery(feature.getQuery().toString(),feature.getEndpoint());
} else {
logger.info("Performing query at " + GlobalSettings.getDefaultSPARQLEndpoint() + " to retrieve all geometries of the SDA Fature [" + feature.getName() + "] ...");
String featureName = FactoryWFS.getInstance().getLoadedModelFeature().expandPrefix(feature.getName());
predicates = factorySDA.getPredicatesSDAFeatures(featureName);
rs = jn.executeQuery(factorySDA.generateGetFeatureSPARQL(featureName, predicates),GlobalSettings.getDefaultSPARQLEndpoint());
}
layerPrefix = FactoryWFS.getInstance().getLoadedModelFeature().shortForm(feature.getName());
layerPrefix = layerPrefix.substring(0,layerPrefix.indexOf(":"));
long countIteration = 0;
if(feature.getOutputFormat().equals("xml")){
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse("wfs/GetFeature_100.xml");
/**
* Build Name Spaces in the XML header.
*/
Element requestElement = document.getDocumentElement();
for (Map.Entry<String, String> entry : FactoryWFS.getInstance().getLoadedModelFeature().getNsPrefixMap().entrySet()) {
requestElement.setAttribute("xmlns:" + entry.getKey(), entry.getValue());
}
logger.info("Creating GetFeature XML document for [" + feature.getName() + "] ...");
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList myNodeList = (NodeList) xpath.compile("//FeatureCollection/text()").evaluate(document, XPathConstants.NODESET);
if(!feature.isFDAFeature()){
Triple triple = new Triple();
triple.setPredicate(GlobalSettings.getGeometryPredicate());
predicates.add(triple);
}
while (rs.hasNext()) {
countIteration++;
QuerySolution soln = rs.nextSolution();
String currentGeometryName = "LODGEO_";
Element currentGeometryElement = document.createElement(FactoryWFS.getInstance().getLoadedModelFeature().shortForm(feature.getName()));
currentGeometryElement.setAttribute("fid", currentGeometryName + "" + countIteration);
Element rootGeometry = document.createElement("gml:featureMember");
for (int i = 0; i < predicates.size(); i++) {
if(feature.isFDAFeature()){
Element elementGeometryPredicate = document.createElement(layerPrefix + ":" + predicates.get(i).getPredicate());
if(predicates.get(i).getPredicate().equals(feature.getGeometryVariable())){
String geometryLiteral = soln.getLiteral("?"+feature.getGeometryVariable()).getString();
String gml;
if(!Utils.isWKT(geometryLiteral)){
gml = geometryLiteral;
} else {
gml = Utils.convertWKTtoGML(geometryLiteral);
}
Element GMLnode = documentBuilder.parse(new ByteArrayInputStream(gml.getBytes())).getDocumentElement();
Node node = document.importNode(GMLnode, true);
elementGeometryPredicate.appendChild(node);
rootGeometry.appendChild(elementGeometryPredicate);
currentGeometryElement.appendChild(elementGeometryPredicate);
rootGeometry.appendChild(currentGeometryElement);
} else {
Element elementAttribute = document.createElement(layerPrefix + ":" + predicates.get(i).getPredicate());
if(soln.get("?"+predicates.get(i).getPredicate().toString()) != null){
if(soln.get("?"+predicates.get(i).getPredicate().toString()).isLiteral()){
if (!soln.getLiteral("?"+predicates.get(i).getPredicate()).getLexicalForm().toString().toUpperCase().equals("NAN")) {
elementAttribute.appendChild(document.createCDATASection(soln.getLiteral("?"+predicates.get(i).getPredicate()).getValue().toString()));
}
} else {
elementAttribute.appendChild(document.createCDATASection(soln.get("?"+predicates.get(i).getPredicate()).toString()));
}
}
currentGeometryElement.appendChild(elementAttribute);
}
} else {
String predicateWithoutPrefix = new String();
predicateWithoutPrefix = this.removePredicateURL(predicates.get(i).getPredicate());
Element elementGeometryPredicate = document.createElement(layerPrefix + ":" + predicateWithoutPrefix);
if (predicates.get(i).getPredicate().equals(GlobalSettings.getGeometryPredicate())) {
if(!Utils.getGeometryType(soln.getLiteral("?" + GlobalSettings.getGeometryVariable()).getString()).equals("INVALID")){
String gml = Utils.convertWKTtoGML(soln.getLiteral("?"+GlobalSettings.getGeometryVariable()).getString());
Element GMLnode = documentBuilder.parse(new ByteArrayInputStream(gml.getBytes())).getDocumentElement();
Node dup = document.importNode(GMLnode, true);
elementGeometryPredicate.appendChild(dup);
rootGeometry.appendChild(elementGeometryPredicate);
currentGeometryElement.appendChild(elementGeometryPredicate);
rootGeometry.appendChild(currentGeometryElement);
} else {
logger.error("The SDA feature [" + soln.get("?geometry") + "] has an invalid geometry literal.");
}
} else {
Element elementAttribute = document.createElement(layerPrefix + ":" + predicateWithoutPrefix);
elementAttribute.appendChild(document.createCDATASection(soln.get("?"+predicateWithoutPrefix).toString()));
currentGeometryElement.appendChild(elementAttribute);
}
}
myNodeList.item(1).getParentNode().insertBefore(rootGeometry, myNodeList.item(1));
}
}
logger.info("XML Document with " + countIteration + " features successfully created.");
getFeatureResponse = Utils.printXMLDocument(document);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Generates a JSON file as output. Geometries are delivered as WKT.
*/
if(feature.getOutputFormat().equals("json")){
StringBuilder json = new StringBuilder();
String jsonEntries = new String();
if(!feature.isFDAFeature()){
Triple triple = new Triple();
triple.setPredicate(factoryFDA.getGeometryPredicate(feature.getQuery()));
predicates.add(triple);
}
jsonEntries = "[\n";
logger.info("Creating JSON document for [" + feature.getName() + "]...");
while (rs.hasNext()) {
QuerySolution soln = rs.nextSolution();
jsonEntries = jsonEntries + " {\n";
for (int i = 0; i < predicates.size(); i++) {
if(soln.get("?"+predicates.get(i).getPredicate()).isLiteral()){
if(soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatype() != null){
/**
* Checks if the literal is of type integer, long, byte or decimal, in order to avoid quotation marks -> "".
*/
if(soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultDecimalType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultLongType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultIntegerType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultByteType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultFloatType())) {
if(!soln.getLiteral("?" + predicates.get(i).getPredicate()).getLexicalForm().toUpperCase().equals("NAN")){
jsonEntries = jsonEntries + " \"" + predicates.get(i).getPredicate().toString() +
"\": " + soln.getLiteral("?" + predicates.get(i).getPredicate()).getValue();
} else {
jsonEntries = jsonEntries + " \"" + predicates.get(i).getPredicate().toString() + "\": 0";
}
}
} else {
jsonEntries = jsonEntries + " \"" + predicates.get(i).getPredicate().toString() +
"\": \"" + soln.getLiteral("?" + predicates.get(i).getPredicate()).getValue().toString().replace("\"", "'") + "\"";
}
} else {
jsonEntries = jsonEntries + " \"" + predicates.get(i).getPredicate().toString() +
"\": \"" + soln.get("?" + predicates.get(i).getPredicate()).toString().replace("\"", "'") + "\"";
}
if(i != predicates.size()-1 ){
jsonEntries = jsonEntries + ",\n";
}
}
if(rs.hasNext()){
jsonEntries = jsonEntries + "\n },\n";
} else {
jsonEntries = jsonEntries + "\n }\n";
}
}
jsonEntries = jsonEntries + "\n]";
json.append(jsonEntries);
getFeatureResponse = json.toString();
}
/**
* Generates a GeoJSON file as output. Geometries are encoded to GeoJSON.
*/
if(feature.getOutputFormat().equals("geojson")){
StringBuilder geojson = new StringBuilder();
String properties = new String();
//int counter=0;
geojson.append("{\"type\":\"FeatureCollection\",\"totalFeatures\":[PARAM_FEATURES],\"features\":[") ;
if(!feature.isFDAFeature()){
Triple triple = new Triple();
triple.setPredicate(factoryFDA.getGeometryPredicate(feature.getQuery()));
predicates.add(triple);
}
logger.info("Creating GeoJSON document for [" + feature.getName() + "]...");
while (rs.hasNext()) {
countIteration++;
QuerySolution soln = rs.nextSolution();
geojson.append("\n{\"type\":\"Feature\",\n \"id\":\"FEATURE_"+ countIteration +"\", \n \"geometry\":");
properties = "\n\"properties\": {\n";
for (int i = 0; i < predicates.size(); i++) {
if(feature.isFDAFeature()){
if(predicates.get(i).getPredicate().equals(feature.getGeometryVariable())){
geojson.append(Utils.convertWKTtoGeoJSON(soln.getLiteral("?" + feature.getGeometryVariable()).getString()));
} else {
/**
* Checks if the literal is of type integer, long, byte or decimal, in order to avoid quotation marks -> "".
*/
if(soln.get("?"+predicates.get(i).getPredicate()).isLiteral()){
if(soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatype() != null){
if(soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultDecimalType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultLongType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultIntegerType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultByteType()) ||
soln.getLiteral("?"+predicates.get(i).getPredicate()).getDatatypeURI().trim().equals(GlobalSettings.getDefaultFloatType())) {
if(!soln.getLiteral("?" + predicates.get(i).getPredicate()).getLexicalForm().toUpperCase().equals("NAN")){
properties = properties + "\n \"" +predicates.get(i).getPredicate().toString()+
"\": " + soln.getLiteral("?"+predicates.get(i).getPredicate()).getValue() + "," ;
}
}
} else {
properties = properties + "\n \"" +predicates.get(i).getPredicate().toString()+
"\": \"" + soln.getLiteral("?"+predicates.get(i).getPredicate()).toString().replace("\"", "'") + "\"," ;
}
} else {
properties = properties + "\n \"" +predicates.get(i).getPredicate().toString()+
"\": \"" + soln.get("?"+predicates.get(i).getPredicate()).toString().replace("\"", "'") + "\"," ;
}
}
} else {
if (predicates.get(i).getPredicate().equals(GlobalSettings.getGeometryPredicate().replaceAll("<", "").replace(">", ""))) {
if(!Utils.getGeometryType(soln.getLiteral("?" + GlobalSettings.getGeometryVariable()).getString()).equals("INVALID")){
geojson.append(Utils.convertWKTtoGeoJSON(soln.getLiteral("?"+GlobalSettings.getGeometryVariable()).getString()) );
} else {
properties = properties + "\"" +predicates.get(i).getPredicate().toString() +
"\": \"" + soln.getLiteral("?"+GlobalSettings.getGeometryVariable()).getString().replace("\"", "'") ;
}
}
}
}
properties = properties.substring(0, properties.length()-1);
geojson.append(properties+"}},");
}
geojson.deleteCharAt(geojson.length()-1);
geojson.append("]}");
getFeatureResponse = geojson.toString().replace("[PARAM_FEATURES]", Long.toString(countIteration));
logger.info("GeoJSON document for [" + feature.getName() + "] with " + countIteration + " geometries successfully created.");
}
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
Date date = new Date();
feature.setLastAccess(dateFormat.format(date));
feature.setSize(getFeatureResponse.getBytes().length);
feature.setGeometries(countIteration);
feature.setGeometryType(geometryType);
this.updateFeatureLog(feature);
return getFeatureResponse;
}
/**
** Private Methods.
**/
private void updateFeatureLog(WFSFeature feature){
String featureLogFile = "logs/features.log";
String line = "";
String splitBy = ";";
String featureFullname = FactoryWFS.getInstance().getLoadedModelFeature().expandPrefix(feature.getName());
StringBuilder fileContent = new StringBuilder();
String featureNewLogData = featureFullname+";"+feature.getLastAccess()+";"+feature.getSize()+";"+feature.getGeometries()+";"+feature.getGeometryType()+"\n";
boolean exists = false;
try {
BufferedReader br = new BufferedReader(new FileReader(featureLogFile));
while ((line = br.readLine()) != null ) {
String[] featureLogLine = line.split(splitBy);
if(featureFullname.equals(featureLogLine[0].trim())){
fileContent.append(featureNewLogData);
exists = true;
} else {
if(!featureLogLine[0].trim().equals("")){
fileContent.append(line+"\n");
}
}
}
if(!exists){
fileContent.append(featureNewLogData);
}
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(featureLogFile, false)));
out.println(fileContent.toString());
out.close();
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String removePredicateURL(String predicate){
return predicate.split("\\P{Alpha}+")[predicate.split("\\P{Alpha}+").length-1];
}
}