// Copyright 2012 Georg-August-Universität Göttingen, Germany // // 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 de.ugoe.cs.autoquest.genericeventmonitor; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.servlet.DefaultServlet; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; import de.ugoe.cs.util.console.Console; /** *
* the servlet deployed in the web server that receives all generic events. The messages are parsed, * validated, and forwarded to the provided message listener. If a message is not valid, it is * discarded. If an event in a message is not valid, it is discarded. Messages are only received via * the POST HTTP method. *
* * @author Patrick Harms */ class GenericEventMonitorServlet extends DefaultServlet { /** */ private static final long serialVersionUID = 1L; /** must be true to do some detailed logging of what the server does */ private static final boolean DO_TRACE = false; /** * the map of event target ids to the concrete target objects. */ private transient Map* initializes the servlet with the message listener to which all events shall be forwarded *
* * @param messageListener the message listener that shall receive all events */ GenericEventMonitorServlet(GenericEventMonitorMessageListener messageListener) { this.messageListener = messageListener; } /** * this implements handling of doPost. For this servlet this means that * the data from the post request will be parsed and validated. * * (non-Javadoc) * @see org.mortbay.jetty.servlet.DefaultServlet#doPost(HttpServletRequest, HttpServletResponse) */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object value = null; try { //InputStream requestInputStream = dumpStreamContent(request.getInputStream()); InputStream requestInputStream = request.getInputStream(); value = JSONValue.parseWithException (new InputStreamReader(requestInputStream, "UTF-8")); if (!(value instanceof JSONObject)) { Console.printerrln("incoming data is not of the expected type --> discarding it"); } else { handleJSONObject((JSONObject) value); } } catch (ParseException e) { Console.printerrln ("could not parse incoming data --> discarding it (" + e.toString() + ")"); } // this must be done to prevent firefox from notifying the "element not found" error response.setContentType("text/plain"); response.getOutputStream().write(' '); } /** ** processes a received JSON object and validates it. If the message is ok, it is forwarded * to the message listener *
* * @param object the JSON object that contains a client message */ private void handleJSONObject(JSONObject object) { if (DO_TRACE) { dumpJSONObject(object, ""); } JSONObject message = assertValue(object, "message", JSONObject.class); if (message == null) { Console.printerrln("incoming data is no valid message --> discarding it"); } else { ClientInfos clientInfos = extractClientInfos(message); if (clientInfos == null) { Console.printerrln ("incoming message does not contain valid client infos --> discarding it"); } else { extractGenericEventTargets(message, clientInfos); GenericEvent[] events = extractGenericEvents(message, clientInfos); if (events == null) { Console.printerrln ("incoming message does not contain valid events --> discarding it"); } else { messageListener.handleEvents(clientInfos, events); Console.println ("handled message of " + clientInfos.getClientId() + " for app " + clientInfos.getAppId() + " (" + events.length + " events)"); } } } } /** ** tries to extract the client infos out of the received JSON object. If this is not fully * possible, an appropriate message is dumped and the whole message is discarded (the method * return null). *
* * @param message the message to parse the client infos from * * @return the client infos, if the message is valid in this respect, or null if not */ private ClientInfos extractClientInfos(JSONObject message) { ClientInfos clientInfos = null; JSONObject infos = assertValue(message, "clientInfos", JSONObject.class); if (infos != null) { String clientId = assertValue((JSONObject) infos, "clientId", String.class); String appId = assertValue((JSONObject) infos, "appId", String.class); if (clientId == null) { Console.printerrln("client infos do not contain a valid client id"); } else if (appId == null) { Console.printerrln("client infos do not contain a valid application id"); } else { clientInfos = new ClientInfos(clientId, appId); } } return clientInfos; } /** ** tries to extract the events out of the received JSON object. If this is not fully * possible, an appropriate message is dumped and the errorprone event is discarded. If no * valid event is found, the whole message is discarded. *
* * @param object the message to parse the events from * @param clientInfos the infos about the client that send the events * * @return the valid events stored in the message, or null if there are none */ private GenericEvent[] extractGenericEvents(JSONObject object, ClientInfos clientInfos) { List* extracts the event target structure from the provided JSON object. *
* * @param object the JSON object to extract the event target structure from * @param clientInfos infos about the client who send the data * * @return the event target structure extracted from the JSON object of which the root nodes * are provided */ private GenericEventTarget[] extractGenericEventTargets(JSONObject object, ClientInfos clientInfos) { JSONObject[] jsonTargets = assertValue(object, "targetStructure", JSONObject[].class); if (jsonTargets != null) { GenericEventTarget[] result = new GenericEventTarget[jsonTargets.length]; for (int i = 0; i < jsonTargets.length; i++) { result[i] = convert(jsonTargets[i], null); } return result; } else { return null; } } /** ** converts a JSON object representing a target to the generic target. Calls * itself recursively to also convert the children of the element, if any. *
* * @param jsonTarget the JSON object to be converted * @param parent the parent target of the converted element, or null, if none * is present. * * @return as described. */ private GenericEventTarget convert(JSONObject jsonTarget, GenericEventTarget parent) { GenericEventTarget result = null; if (jsonTarget != null) { String targetId = assertValue(jsonTarget, "targetId", String.class); Map* converts a value in the provided object matching the provided key to the provided type. If * there is no value with the key or if the value can not be transformed to the provided type, * the method returns null. *
* * @param object the object to read the value from * @param key the key of the value * @param clazz the type to which the value should be transformed * * @return the value or null if either the value does not exist or if it can not be transformed * to the expected type */ @SuppressWarnings("unchecked") private* determines other parameters provided in the JSON object. The parameters with the provided * keys to ignore are not put into the resulting map. *
* * @param object the object, from which parameters shall be read * @param ignoredKeys the parameters not to be put into the resulting map * * @return a map with the parameters contained in the provided JSON object */ private Map* convenience method for dumping the content of a stream and returning a new stream * containing the same data. *
* * @param inputStream the stream to be dumped and copied * @return the copy of the stream * * @throws IOException if the stream can not be read */ /* private InputStream dumpStreamContent(ServletInputStream inputStream) throws IOException { List* convenience method for dumping an object to std out. If the object is a JSON object, it is * deeply analyzed and its internal structure is dumped as well. *
* * @param object the object to dump * @param indent the indentation to be used. */ private void dumpJSONObject(Object object, String indent) { if (object instanceof JSONArray) { boolean arrayContainsJSONObjects = false; for (Object arrayElem : (JSONArray) object) { if (arrayElem instanceof JSONObject) { arrayContainsJSONObjects = true; break; } } if (arrayContainsJSONObjects) { System.out.println(); System.out.print(indent); System.out.println('['); System.out.print(indent); System.out.print(' '); } else { System.out.print(' '); System.out.print('['); } int index = 0; for (Object arrayElem : (JSONArray) object) { if (index++ > 0) { System.out.print(","); if (arrayContainsJSONObjects) { System.out.println(); System.out.print(indent); } System.out.print(' '); } dumpJSONObject(arrayElem, indent + " "); } if (arrayContainsJSONObjects) { System.out.println(); System.out.print(indent); } System.out.print(']'); } else if (object instanceof JSONObject) { System.out.println(" {"); @SuppressWarnings("unchecked") Set