package com.esri.geoevent.solutions.adapter.cot;
/*
* #%L
* CoTAdapter.java - Esri :: AGES :: Solutions :: Adapter :: CoT - Esri - 2013
* org.codehaus.mojo-license-maven-plugin-1.5
* $Id: update-file-header-config.apt.vm 17764 2012-12-12 10:22:04Z tchemit $
* $HeadURL: https://svn.codehaus.org/mojo/tags/license-maven-plugin-1.5/src/site/apt/examples/update-file-header-config.apt.vm $
* %%
* Copyright (C) 2013 - 2014 Esri
* %%
* 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.
* #L%
*/
//import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.GeometryEngine;
import com.esri.core.geometry.MapGeometry;
import com.esri.core.geometry.SpatialReference;
import com.esri.ges.adapter.AdapterDefinition;
import com.esri.ges.adapter.InboundAdapterBase;
import com.esri.ges.core.ConfigurationException;
import com.esri.ges.core.component.ComponentException;
import com.esri.ges.core.geoevent.FieldDefinition;
import com.esri.ges.core.geoevent.FieldException;
import com.esri.ges.core.geoevent.FieldGroup;
import com.esri.ges.core.geoevent.FieldType;
import com.esri.ges.core.geoevent.GeoEvent;
import com.esri.ges.core.geoevent.GeoEventDefinition;
import com.esri.ges.core.property.Property;
import com.esri.core.geometry.Point;
public class CoTAdapter extends InboundAdapterBase
{
private static final Log log = LogFactory.getLog(CoTAdapter.class);
private static final int GCS_WGS_1984 = 4326;
private String guid;
// this ArrayList contains ALL the type defs but it must be used differently
// depending on what you are looking for.
private ArrayList<CoTTypeDef> coTTypeMap;
private ByteArrayOutputStream jsonBuffer;
private JsonGenerator generator;
private JsonFactory factory;
private boolean firstVertex;
private double cachedLat;
private double cachedLon;
private double cachedHae;
private static final int CAPACITY = 1 * 1024 * 1024;
private HashMap<String,String> buffers = new HashMap<String,String>();
private int maxBufferSize;
private SAXParserFactory saxFactory;
@SuppressWarnings("unused")
private SAXParser saxParser;
@SuppressWarnings("unused")
private MessageParser messageParser;
public CoTAdapter(AdapterDefinition adapterDefinition, String guid) throws ConfigurationException, ComponentException
{
super(adapterDefinition);
this.guid = guid;
messageParser = new MessageParser(null);
saxFactory = SAXParserFactory.newInstance();
try
{
saxParser = saxFactory.newSAXParser();
} catch (ParserConfigurationException e)
{
e.printStackTrace();
saxParser = null;
} catch (SAXException e)
{
e.printStackTrace();
saxParser = null;
}
}
@Override
public void afterPropertiesSet()
{
if (hasProperty(CoTAdapterService.MAXIMUM_BUFFER_SIZE_LABEL))
{
Property bufSizeProperty = getProperty(CoTAdapterService.MAXIMUM_BUFFER_SIZE_LABEL);
if( bufSizeProperty != null )
{
int maxBufferSize = (Integer)bufSizeProperty.getValue();
if( maxBufferSize <= 0 )
{
log.error("Cannot set the maximum buffer size to " + maxBufferSize );
}
else
this.maxBufferSize = maxBufferSize;
}
}
if (hasProperty(CoTAdapterService.COT_TYPES_PATH_LABEL))
{
Property cotTypesPathProperty = getProperty(CoTAdapterService.COT_TYPES_PATH_LABEL);
if( cotTypesPathProperty != null )
{
String userDefinedPath = cotTypesPathProperty.getValueAsString();
if( userDefinedPath != null && (!userDefinedPath.equals("")))
{
try
{
this.coTTypeMap = CoTUtilities.getCoTTypeMap(new FileInputStream(((Property) cotTypesPathProperty).getValueAsString()));
log.info("CotTypes.xml path will be set to: " + userDefinedPath);
} catch (Exception e)
{
this.coTTypeMap = null;
log.error("Problem loading the user-specified CoTTypes.xml file.",e);
}
}
}
}
if( this.coTTypeMap == null )
{
try
{
String defaultPath = "CoTTypes/CoTtypes.xml";
this.coTTypeMap = CoTUtilities.getCoTTypeMap(this.getClass().getClassLoader().getResourceAsStream(defaultPath));
log.info("Default CoTtypes.xml definitions were loaded successfully.");
} catch (Exception e1)
{
log.error("Problem loading the default CoTTypes.xml file.",e1);
}
}
}
@Override
public void receive(ByteBuffer buf, String channelId)
{
buf.mark();
int size = buf.remaining();
if (size < 1)
return;
byte[] data = new byte[size];
buf.get(data, 0, size);
//System.out.println(" \n");
//System.out.println("Read " + size + " bytes");
String xml = new String(data);
parseUsingDocument(xml,channelId);
//parseUsingStream(buf);
}
//Might need this block for future enhancements...
/*private void parseUsingStream( ByteBuffer bb )
{
try
{
int remaining = bb.remaining();
if( remaining <= 0 )
return;
byte[] bytes = new byte[remaining];
bb.get(bytes);
saxParser.parse( new ByteArrayInputStream(bytes), messageParser);
bytes = null;
} catch (SAXException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
}*/
private void parseUsingDocument( String xml, String channelId )
{
if( buffers.containsKey(channelId) )
{
String temp = buffers.remove(channelId);
temp = temp + xml;
if( temp.length() > maxBufferSize )
{
log.error("The size of the incoming xml message exceeds the configured maximum buffer size of "+maxBufferSize+". The buffer contents will be discarded to make room for incoming data.");
temp = scanForEvent(temp); // Look for something that looks like a new message.
if( temp == null ) // If we didn't find something that looks like a new message, just start with the xml that was passed in (discarding the buffered data).
temp = xml;
}
xml = temp;
}
try
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource source = new InputSource();
source.setCharacterStream(new StringReader(xml));
Document doc = db.parse(source);
NodeList nodeList = doc.getElementsByTagName("event");
if (nodeList != null)
{
for( int i = 0; i < nodeList.getLength(); i++ )
{
GeoEvent msg = geoEventCreator.create(guid);
Element e = (Element) nodeList.item(i);
String version = e.getAttribute("version");
if (!version.isEmpty())
msg.setField(0, Double.valueOf(version));
msg.setField(1, e.getAttribute("uid"));
String type = e.getAttribute("type");
msg.setField(2, type);
msg.setField(3, CoTUtilities.getSymbolFromCot(type));
//System.out.println("2525b from type: "+ CoTUtilities.getSymbolFromCot(type));
//System.out.println("type: " + e.getAttribute("type") " -> " + convertType(e.getAttribute("type")));
msg.setField(4, convertType(e.getAttribute("type")));
//System.out.println("how: " + e.getAttribute("how") + " -> "+ convertHow(e.getAttribute("how")));
msg.setField(5, e.getAttribute("how"));
msg.setField(6, convertHow(e.getAttribute("how")));
msg.setField(7, parseCoTDate(e.getAttribute("time")));
msg.setField(8, parseCoTDate(e.getAttribute("start")));
msg.setField(9, parseCoTDate(e.getAttribute("stale")));
msg.setField(10, e.getAttribute("access"));
msg.setField(11, e.getAttribute("opex"));
msg.setField(12, convertOpex(e.getAttribute("opex")));
//System.out.println("opex: " + e.getAttribute("opex" + " is a: " + convertOpex(e.getAttribute("opex")));
msg.setField(13, e.getAttribute("qos"));
msg.setField(14, convertQos(e.getAttribute("qos")));
//System.out.println("qos: " + e.getAttribute("qos") + " -> " convertQos(e.getAttribute("qos")));
NodeList points = e.getElementsByTagName("point");
if (points.getLength() > 0)
{
Element pointElement = (Element) points.item(0);
MapGeometry geom = createGeometry(pointElement);
msg.setField(15, geom);
//msg.setField( 15, geom.toJson() );
}
GeoEventDefinition ged = msg.getGeoEventDefinition();
traverseBranch( findChildNodes(e, "detail" ).get(0), msg, ged.getFieldDefinition("detail") );
geoEventListener.receive( msg );
}
}
} catch (Exception e)
{
if( e.getMessage() != null && e.getMessage().equals("XML document structures must start and end within the same entity.") )
{
if( xml != null )
buffers.put(channelId, xml);
return;
}
log.error("Error while parsing CoT message. For details, set log level to DEBUG.",e);
log.debug("Error while parsing the message : "+xml);
return;
}
}
private String scanForEvent(String buffer)
{
int index = buffer.indexOf( "<?xml version'", 1 );
if( index > 0 )
return buffer.substring(index);
return null;
}
@SuppressWarnings("incomplete-switch")
private void traverseBranch(Node node, FieldGroup fieldGroup, FieldDefinition fieldDefinition) throws FieldException
{
if( node == null ) return;
//System.out.println("Examining node named \""+node.getNodeName()+"\"");
FieldType fieldType = fieldDefinition.getType();
switch( fieldType )
{
case Group:
FieldGroup childFieldGroup = fieldGroup.createFieldGroup(fieldDefinition.getName());
fieldGroup.setField( fieldDefinition.getName(), childFieldGroup);
for( FieldDefinition childFieldDefinition : fieldDefinition.getChildren() )
{
String childName = childFieldDefinition.getName();
List<Node> childNodes = findChildNodes( node, childName );
if( childNodes.size() > 0 )
{
for( Node childNode : childNodes )
traverseBranch( childNode, childFieldGroup, childFieldDefinition );
}
else
traverseBranch( node, childFieldGroup, childFieldDefinition );
}
break;
case String:
String value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
fieldGroup.setField(fieldDefinition.getName(), value);
break;
case Integer:
value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
fieldGroup.setField(fieldDefinition.getName(), new Integer(value));
break;
case Double:
value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
fieldGroup.setField(fieldDefinition.getName(), new Double(value));
break;
case Boolean:
value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
fieldGroup.setField(fieldDefinition.getName(), new Boolean(value));
break;
case Date:
value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
{
Date date = new Date();
try
{
date = parseCoTDate(value);
}catch(Exception ex)
{
}
fieldGroup.setField(fieldDefinition.getName(), date);
}
break;
case Geometry:
MapGeometry geometry = createGeometry(node);
if( geometry != null )
fieldGroup.setField(fieldDefinition.getName(), geometry);
break;
case Long:
value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
fieldGroup.setField(fieldDefinition.getName(), new Long(value));
break;
case Short:
value = getAttribute( node, fieldDefinition.getName() );
if( value != null )
fieldGroup.setField(fieldDefinition.getName(), new Integer(value));
break;
}
}
private List<Node> findChildNodes( Node node, String childName)
{
ArrayList<Node> children = new ArrayList<Node>();
NodeList childNodes = node.getChildNodes();
for( int i = 0; i < childNodes.getLength(); i++ )
{
Node child = childNodes.item(i);
if( child.getNodeName().equals(childName) )
children.add(child);
}
return children;
}
private String getAttribute( Node node, String attributeName )
{
NamedNodeMap attributes = node.getAttributes();
for(int i = 0; i < attributes.getLength(); i++ )
{
Node attributeNode = attributes.item(i);
if( attributeNode.getNodeName().equals(attributeName))
{
return attributeNode.getNodeValue();
}
}
return null;
}
private String convertQos(String type) {
StringBuilder sb = new StringBuilder();
Matcher matcher;
for (CoTTypeDef cd : this.coTTypeMap) {
if (cd.isPredicate() && cd.getValue().startsWith("q.")) {
Pattern pattern = Pattern.compile(cd.getKey());
matcher = pattern.matcher(type);
if (matcher.find()) {
sb.append(cd.getValue() + " ");
}
}
}
return this.filterOutDots(sb.toString());
}
private String convertOpex(String type) {
Matcher matcher;
for (CoTTypeDef cd : this.coTTypeMap) {
if (cd.isPredicate() && cd.getValue().startsWith("o.")) {
Pattern pattern = Pattern.compile(cd.getKey());
matcher = pattern.matcher(type);
if (matcher.find()) {
return this.filterOutDots(cd.getValue());
}
}
}
// no match was found
return "";
}
private String convertHow(String type) {
Matcher matcher;
for (CoTTypeDef cd : this.coTTypeMap) {
if (!cd.isPredicate()) {
Pattern pattern = Pattern.compile(cd.getKey());
matcher = pattern.matcher(type);
if (matcher.find()) {
return this
.filterOutDots(appendToHow(type) + cd.getValue());
}
}
}
// no match was found
return "";
}
private String appendToHow(String type) {
Matcher matcher;
StringBuffer sb = new StringBuffer();
for (CoTTypeDef cd : this.coTTypeMap) {
// now only consider the value if it is:
// 1. a predicate
if (cd.isPredicate()) {
Pattern pattern = Pattern.compile(cd.getKey());
matcher = pattern.matcher(type);
if (matcher.find()) {
// now only append the value if it is:
// 1. not prefixed with a dot notation
if (cd.getValue().startsWith("h.")) {
sb.append(cd.getValue() + " ");
}
}
}
}
return sb.toString();
}
private String convertType(String type) {
Matcher matcher;
for (CoTTypeDef cd : this.coTTypeMap) {
if (!cd.isPredicate()) {
Pattern pattern = Pattern.compile(cd.getKey());
matcher = pattern.matcher(type);
if (matcher.find()) {
return this.filterOutDots(appendToType(type)
+ cd.getValue());
}
}
}
// no match was found
return "";
}
private String appendToType(String type) {
Matcher matcher;
StringBuffer sb = new StringBuffer();
for (CoTTypeDef cd : this.coTTypeMap) {
// now only consider the value if it is:
// 1. a predicate
// 2. not prefixed with a dot notation
if (cd.isPredicate()
&& !(cd.getValue().startsWith("h.")
|| cd.getValue().startsWith("t.")
|| cd.getValue().startsWith("r.")
|| cd.getValue().startsWith("q.") || cd.getValue()
.startsWith("o."))) {
Pattern pattern = Pattern.compile(cd.getKey());
matcher = pattern.matcher(type);
if (matcher.find()) {
sb.append(cd.getValue() + " ");
}
}
}
return sb.toString();
}
/*
* The following method's only purpose is to take out the dot notations eg:
* "h.whatever" will indicate that "whatever" is in the "how" category
*/
private String filterOutDots(String s) {
String sStageOne = s.replace("h.", "").replace("t.", "")
.replace("r.", "").replace("q.", "").replace("o.", "");
String[] s2 = sStageOne.trim().split(" ");
ArrayList<String> l1 = new ArrayList<String>();
for (String item : s2) {
l1.add(item);
}
ArrayList<String> l2 = new ArrayList<String>();
Iterator<String> iterator = l1.iterator();
while (iterator.hasNext()) {
String o = (String) iterator.next();
if (!l2.contains(o))
l2.add(o);
}
StringBuffer sb = new StringBuffer();
for (String item : l2) {
sb.append(item);
sb.append(" ");
}
return sb.toString().trim().toLowerCase();
}
public Date parseCoTDate(String dateString) throws Exception
{
if (!dateString.isEmpty())
{
DateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
formatter1.setTimeZone(TimeZone.getTimeZone("Zulu"));
DateFormat formatter2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
formatter2.setTimeZone(TimeZone.getTimeZone("Zulu"));
Date date = null;
try
{
if( date == null )
date = (Date) formatter1.parse(dateString);
}catch( ParseException ex )
{
}
try
{
if( date == null )
date = (Date) formatter2.parse(dateString);
}catch( ParseException ex )
{
}
return date;
}
return null;
}
// temp code to show the details field
public static String elementToString(Node n) {
String name = n.getNodeName();
short type = n.getNodeType();
if (Node.CDATA_SECTION_NODE == type) {
return "<![CDATA[" + n.getNodeValue() + "]]>";
}
if (name.startsWith("#")) {
return "";
}
StringBuffer sb = new StringBuffer();
sb.append('<').append(name);
NamedNodeMap attrs = n.getAttributes();
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
sb.append(' ').append(attr.getNodeName()).append("=\"")
.append(attr.getNodeValue()).append("\"");
}
}
String textContent = null;
NodeList children = n.getChildNodes();
if (children.getLength() == 0) {
if ((textContent = n.getTextContent()) != null
&& !"".equals(textContent)) {
sb.append(textContent).append("</").append(name).append('>');
} else {
sb.append("/>").append('\n');
}
} else {
sb.append('>').append('\n');
boolean hasValidChildren = false;
for (int i = 0; i < children.getLength(); i++) {
String childToString = elementToString(children.item(i));
if (!"".equals(childToString)) {
sb.append(childToString);
hasValidChildren = true;
}
}
if (!hasValidChildren
&& ((textContent = n.getTextContent()) != null)) {
sb.append(textContent);
}
sb.append("</").append(name).append('>');
}
return sb.toString();
}
private MapGeometry createGeometry(Node node)
{
if( node.getNodeName().equals("point") )
{
String lat = getAttribute(node,"lat");
String lon = getAttribute(node, "lon");
String hae = getAttribute(node, "hae");
Point pt = new Point();
if (!lat.isEmpty() && !lon.isEmpty()) {
if (hae.isEmpty()) {
// SpatialReference sr =
pt.setX(Double.valueOf(lon));
pt.setY(Double.valueOf(lat));
SpatialReference srOut = SpatialReference.create(4326);
MapGeometry mapGeo = new MapGeometry(pt, srOut);
return mapGeo;
} else {
pt.setX(Double.valueOf(lon));
pt.setY(Double.valueOf(lat));
pt.setZ(Double.valueOf(hae));
SpatialReference srOut = SpatialReference.create(4326);
MapGeometry mapGeo = new MapGeometry(pt, srOut);
return mapGeo;
}
}
}
if( node.getNodeName().equals("shape") )
{
try
{
List<Node> polylines = findChildNodes( node, "polyline" );
for( Node polyline : polylines)
{
startNewPolygon();
List<Node> vertices = findChildNodes( polyline, "vertex" );
for( Node vertex : vertices )
{
double lat = 0;
double lon = 0;
double hae = 0;
String s = null;
s = getAttribute( vertex, "lat");
if( s != null )
lat = Double.parseDouble( s );
s = getAttribute( vertex, "lon");
if( s != null )
lon = Double.parseDouble( s );
s = getAttribute( vertex, "hae");
if( s != null )
hae = Double.parseDouble( s );
addVertexToPolygon( lat, lon, hae );
}
String geometryString = closePolygon();
String jsonString = geometryString.substring(0, geometryString.length()-1) + ",\"spatialReference\":{\"wkid\":"+GCS_WGS_1984+"}}";
//System.out.println("json string = \""+jsonString+"\"");
JsonFactory jf = new JsonFactory();
JsonParser jp = jf.createJsonParser(jsonString);
MapGeometry geometry = GeometryEngine.jsonToGeometry(jp);
//Geometry geometry = spatial.fromJson(jsonString);
return geometry;
}
}catch(Exception ex)
{
generator = null;
return null;
}
}
return null;
}
private void startNewPolygon() throws IOException
{
if( generator == null )
initializeJsonGenerator();
generator.writeStartObject();
generator.writeArrayFieldStart("rings");
generator.writeStartArray();
firstVertex = true;
}
private void initializeJsonGenerator() throws IOException
{
factory = new JsonFactory();
jsonBuffer = new ByteArrayOutputStream(CAPACITY);
generator = factory.createJsonGenerator(jsonBuffer);
}
private void addVertexToPolygon(double lat, double lon, double hae) throws JsonGenerationException, IOException
{
if( firstVertex )
{
firstVertex = false;
cachedLat = lat;
cachedLon = lon;
cachedHae = hae;
firstVertex = false;
}
generator.writeStartArray();
generator.writeNumber(lon);
generator.writeNumber(lat);
//generator.writeNumber(hae);
generator.writeEndArray();
}
private String closePolygon() throws IOException
{
if(!firstVertex)
addVertexToPolygon( cachedLat, cachedLon, cachedHae );
generator.writeEndArray();
generator.writeEndArray();
generator.writeEndObject();
generator.flush();
String jsonString = jsonBuffer.toString();
jsonBuffer.reset();
return jsonString;
}
@Override
protected GeoEvent adapt(ByteBuffer buffer, String channelId)
{
// Do nothing, this will not be called since we have overloaded the receive() function.
return null;
}
}