package eu.smartfp7.EdgeNode; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.lightcouch.CouchDbClient; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import eu.smartfp7.utils.LRUCache; import eu.smartfp7.utils.Xml; /** * Servlet implementation class TelestoFeed */ @WebServlet(name = "telestoFeed", description = "Reads temperature and humidity from Telesto server and updates respective feed", urlPatterns = { "/telestoFeed" }) public class TelestoFeed extends HttpServlet { private static final long serialVersionUID = 1L; // CouchDB access parameters private CouchDbClient feedsClient = null; private String server, user, pass; private int port; Map<String, TelestoTaskData> telestoList = null; /** * @see HttpServlet#HttpServlet() */ public TelestoFeed() { super(); } /** * @see Servlet#init(ServletConfig) */ public void init(ServletConfig config) throws ServletException { try { super.init(config); } catch (ServletException e) { System.err.println("Can not initialize servlet"); return; } // Read couchdb properties from file and initialise the client for feeds Properties dbProps = new Properties(); try { dbProps.load(getServletContext().getResourceAsStream("/WEB-INF/couchdb.properties")); } catch (IOException e1) { System.err.println("Can not open couchdb properties file"); return; } server = dbProps.getProperty("server"); port = Integer.parseInt(dbProps.getProperty("port")); user = dbProps.getProperty("user"); pass = dbProps.getProperty("pass"); // Test for incorrect user/pass or CouchDB server offline try { feedsClient = new CouchDbClient("feeds", true, "http", server, port, user, pass); } catch (Exception e) { System.err .println("Could not connect to CouchDB, check that the server is running and that the correct username/pass is set"); System.err.println("Current configuration: server=" + server + ", port=" + port + ", user= " + user + ", pass= " + pass); return; } telestoList = Collections.synchronizedMap(new HashMap<String, TelestoTaskData>()); } /** * @see Servlet#destroy() */ public void destroy() { // Stop all running timers cancelTimers(); // Close feeds connection if (feedsClient != null) { feedsClient.shutdown(); } } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String accept = request.getHeader("Accept"); if (accept != null && accept.contains("application/json")) response.setContentType("application/json; charset=utf-8"); else response.setContentType("text/plain; charset=utf-8"); PrintWriter out = response.getWriter(); String action = request.getParameter("action"); if (action == null) { try { response.sendRedirect("telestoFeed.html"); } catch (IOException e1) { System.err.println("doGet IOException: Can not redirect"); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); out.println("{\"error\":\"action parameter not specified\"}"); } return; } int res = -1; String servername = null; if (action.equals("list")) res = listRunningTelesto(out); else { // Read the server name servername = request.getParameter("server"); if (servername == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); out.println("{\"error\":\"server parameter not specified\"}"); return; } } if (action.equals("start")) { res = startTelesto(servername, out, request); } else if (action.equals("stop")) { res = stopTelesto(servername, out); } else { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); out.println("{\"error\":\"Unknown action, should be one of: 'list', 'start', 'stop', 'delete', 'delete_all'\"}"); return; } // error messages will be printed by the function if (res < 0) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } else if (res > 0) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); } } // All below functions return 0 if ok -1 if server error 1 if bad request int listRunningTelesto(PrintWriter out) { if (telestoList == null || feedsClient == null) { out.println("{\"error\":\"Server not correctly initialised\"}"); return -1; } int len; StringBuilder names = new StringBuilder(); for (String name : telestoList.keySet()) { names.append("\"" + name + "\","); } // Delete last comma character in name list if ((len = names.length()) > 0) names.deleteCharAt(len - 1); out.println("{\"names\":[" + names.toString() + "]}"); return 0; } /* * Returns 0 on success, 1 on bad input, -1 on error and prints description in out */ int startTelesto(String servername, PrintWriter out, HttpServletRequest request) { if (telestoList == null || feedsClient == null) { out.println("{\"error\":\"Server not correctly initialised\"}"); return -1; } if (telestoList.size() > 30) { out.println("{\"error\":\"Too many telesto feeds currently running (" + telestoList.size() + "), please stop some before retrying\"}"); return 1; } String dbname = request.getParameter("feedname"); // If the user provides a new name, clean and use it, else use the default if (dbname != null) { dbname = Common.cleanString(dbname); } else dbname = "temphumfeed"; // Do not start a new telesto feed if already running if (telestoList.containsKey(servername)) { out.println("{\"status\":\"Telesto feed on '" + servername + "' already running\"}"); return 0; } // Put a blank entry to prevent subsequent calls from initialising the same data telestoList.put(servername, null); TelestoTaskData data = initData(servername, dbname, out, request); if (data == null) { telestoList.remove(servername); return -1; } // Add a new entry on telestoList to avoid starting more threads on consecutive requests telestoList.put(servername, data); // In feeds database create the feed description if it doesn't exist for the new feed // Get the revision of new feed description document, null means it does not exist String rev = feedsClient.getRevision(dbname); if (rev == null) { // Get the original feed description JsonObject feedObj = new JsonObject(); // Change the document id to match new feed feedObj.addProperty("_id", dbname); feedObj.addProperty("Feed", "test " + servername); try { feedsClient.save(feedObj); } catch (Exception e) { telestoList.remove(dbname); // release previously initialised clients cancelTelesto(data); out.println("{\"error\":\"Could not copy feed description from '" + servername + "' to '" + dbname + "' \"}"); return -1; } } // Set the timer to save the next doc data.tm = new Timer("timer_" + dbname); data.tm.schedule(new TelestoTimer(dbname), 0, data.interval); out.println("{\"status\":\"Started telesto feed for '" + servername + "' as '" + dbname + "'\"}"); return 0; } TelestoTaskData initData(String servername, String dbname, PrintWriter out, HttpServletRequest request) { TelestoTaskData data = new TelestoTaskData(); // Default interval of 60 seconds long interval = 60000; if (request.getParameter("interval") != null) { try { long intrv2 = Long.parseLong(request.getParameter("interval")); if (intrv2 > 0) interval = intrv2 * 1000; } catch (Exception ignore) { } } int cacheSize = (int) (3 * interval / 1000); data.srcName = servername; data.interval = interval; // If we have consecutive errors for 60 minutes, stop // 60mins = 60*60*1000 = 3.6E6 data.maxerrors = (int) (3.6E6 / data.interval) + 1; data.srcClient = new DefaultHttpClient(); data.sinceHum = 0; data.sinceTemp = 0; if (data.srcClient == null) { // No connection could be made to source client out.println("{\"error\":\"Could not create HTTP client\"}"); return null; } data.httpget = new HttpGet(); // Open a connection to the destination DB try { // Create the new database if it does not exist data.dstClient = new CouchDbClient(dbname, true, "http", server, port, user, pass); } catch (Exception e) { telestoList.remove(dbname); cancelTelesto(data); // Couldn't connect to target db out.println("{\"error\":\"Could not access DB '" + dbname + "' \"}"); return null; } // If the new db does not have design document for view, add it // TODO: copy from default if (!data.dstClient.contains("_design/get_data")) { String viewDoc = "{\n" + "\t\"_id\": \"_design/get_data\",\n" + "\t\"language\": \"javascript\",\n" + "\t\"views\": {\n" + "\t\t \"by_date\": {\n" + "\t\t\t \"map\": \"function(doc) {\\nif(doc.timestamp && doc.data) {\\nemit(doc.timestamp, doc.data);\\n}\\n}\"\n" + "\t\t }\n" + "\t}\n" + "}\n"; try { data.dstClient.saveJsonText(viewDoc); } catch (Exception e) { telestoList.remove(dbname); // release previously initialised clients cancelTelesto(data); out.println("{\"error\":\"Could not add design document in DB '" + dbname + "' \"}"); return null; } } data.cache = new LRUCache<String, String>(cacheSize); // Fill the cache with the latest data from CouchDB fillCache(data); return data; } void fillCache(TelestoTaskData data) { List<JsonObject> resList = null; String keyStr; try { //Get the most recent documents resList = data.dstClient.view("get_data/by_date").descending(true).limit(data.cache.getLimit()) .query(JsonObject.class); } catch (Exception e) { return; } if (resList == null || resList.size() < 1) { return; } for (JsonObject o : resList) { //Store them in the cache try { keyStr = o.get("value").toString(); data.cache.put(keyStr, null); } catch (Exception e) { continue; } } System.out.println("Cache for '" + data.srcName + "' loaded successfully with " + data.cache.size() + " documents"); } class TelestoTimer extends TimerTask { String name; TelestoTimer(String name) { this.name = name; } @Override public void run() { TelestoTaskData data = telestoList.get(name); // System.out.println("Running " + name + ", count = " + data.count++ + ", errorcount =" + data.errorcount); String resp; resp = checkServer(data, "Humidity", data.sinceHum); processResp(resp, data, "Humidity"); resp = checkServer(data, "Temperature", data.sinceTemp); processResp(resp, data, "Temperature"); return; } private void processResp(String resp, TelestoTaskData data, String type) { if (resp == null) return; String respJson = Xml.convertToJson(resp); if ("error".equals(respJson)) { System.err.println("Can not convert to JSON, data was: \n" + resp); return; } JsonObject jsonObj = new JsonParser().parse(respJson).getAsJsonObject(); JsonArray arr; try { arr = jsonObj.get("generalSearch").getAsJsonObject().get("Post").getAsJsonArray(); String keyString, dateStr; JsonObject o2; long timestamp; for (JsonElement e : arr) { try { // System.out.println(e.toString()); o2 = e.getAsJsonObject(); dateStr = o2.get("CreationDate").getAsString(); o2.remove("CreationDate"); keyString = o2.toString(); if (!data.cache.containsKey(keyString)) { timestamp = Common.string2millis(dateStr); data.cache.put(keyString, null); sendData(timestamp, o2, data); } } catch (Exception ignore) { continue; } } } catch (Exception ignore) { return; } } private void sendData(long timestamp, JsonObject e, TelestoTaskData data) { JsonObject o = new JsonObject(); String UTCdate = Common.millis2String(timestamp); int maxretries = 10; // Document should not exist in DB, if it exists increment the milliseconds by one while (data.dstClient.contains(UTCdate) && --maxretries > 0) { UTCdate = Common.millis2String(++timestamp); } if (maxretries == 0) return; // System.out.println(timestamp + ":" + UTCdate); o.addProperty("_id", UTCdate); o.addProperty("timestamp", timestamp); o.add("data", e); data.dstClient.save(o); } String checkServer(TelestoTaskData data, String type, long sinceTime) { StringBuilder resultXML = null; try { data.httpget.setURI(new URI("http://" + data.srcName + "/SMART_EdgeNode/EdgeNode/DataFeeds/1/measurements/" + type + "/searchAfterDate?endDate=" + sinceTime)); } catch (URISyntaxException e1) { return null; } try { // Execute HTTP request // System.out.println("executing request " + data.httpget.getURI()); HttpResponse response = data.srcClient.execute(data.httpget); // System.out.println("----------------------------------------"); // System.out.println(response.getStatusLine()); // System.out.println("----------------------------------------"); // Get hold of the response entity HttpEntity entity = response.getEntity(); // If the response does not enclose an entity, there is no need // to bother about connection release if (entity != null) { resultXML = new StringBuilder(2000); InputStream instream = entity.getContent(); try { // Store the response BufferedReader rd = new BufferedReader(new InputStreamReader(instream, "UTF-8")); String line = ""; while ((line = rd.readLine()) != null) { resultXML.append(line); } } catch (IOException ex) { // In case of an IOException the connection will be released // back to the connection manager automatically ex.printStackTrace(); } catch (RuntimeException ex) { // In case of an unexpected exception you may want to abort // the HTTP request in order to shut down the underlying // connection immediately. data.httpget.abort(); ex.printStackTrace(); } finally { // Closing the input stream will trigger connection release try { instream.close(); } catch (Exception ignore) { } } } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } if (resultXML == null || resultXML.length() == 0) { // After many consecutive errors, cancel the run if (data.errorcount++ > data.maxerrors) cancelTelesto(data); return null; } else { // reset the error counter data.errorcount = 0; return resultXML.toString(); } } } int stopTelesto(String name, PrintWriter out) { if (telestoList == null) return -1; if (!telestoList.containsKey(name)) { if (out != null) out.println("{\"error\":\"Social feed with name '" + name + "' not running\"}"); return 1; } TelestoTaskData data = telestoList.get(name); cancelTelesto(data); telestoList.remove(name); if (out != null) out.println("{\"status\":\"Stopped social feed with name '" + name + "'\"}"); return 0; } private void cancelTelesto(TelestoTaskData data) { if (data==null) return; // Stop timer if (data.tm != null) data.tm.cancel(); // Close HTTP and CouchDB clients if (data.srcClient != null) data.srcClient.getConnectionManager().shutdown(); if (data.dstClient != null) data.dstClient.shutdown(); } // Stop all running timers private void cancelTimers() { synchronized (telestoList) { for (String name : telestoList.keySet()) { cancelTelesto(telestoList.get(name)); } telestoList.clear(); } } class TelestoTaskData { Timer tm; HttpClient srcClient; String srcName; HttpGet httpget; CouchDbClient dstClient; LRUCache<String, String> cache; int count, errorcount, maxerrors; long interval; long sinceHum, sinceTemp; } }