/*
* "Copyright (c) 2010-11 The Regents of the University of California.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice, the following
* two paragraphs and the author appear in all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
* CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
*
* Author: Jorge Ortiz (jortiz@cs.berkeley.edu)
* IS4 release version 1.0
*/
package local.rest.handlers;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.Date;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.net.*;
import java.util.*;
import java.sql.*;
import is4.*;
import local.db.*;
import local.json.validator.*;
import net.sf.json.*;
import com.sun.net.httpserver.*;
public class SmapSourceHandler extends Filter implements HttpHandler {
protected static Logger logger = Logger.getLogger(SmapSourceHandler.class.getPackage().getName());
//This addresses the HttpContext switch bug
//For every call to create a context in the httpServer after the root, the HttpContext object changes, and the filter
//is no longer used in the new object.
protected HttpContext thisContext = null;
protected static String URI = null;
//maps the deployment name to an Hashtable that maps sMAP device name to IS4 publisher identifier
//Example: acme302-->067e6162-3b6f-4ae2-a171-2470b63dff00
// Usually acme devices are named with an integer(i.e. 302), this process appends
// the model in front of the id.
public Hashtable<String, Hashtable<String, String>> allDeployments = new Hashtable<String, Hashtable<String,String>>();
public static String is4URL = "http://smote.cs.berkeley.edu:8080";
public SmapSourceHandler(String uri){
URI = uri;
if (System.getenv("IS4_HOSTNAME") != null && System.getenv("IS4_PORT") !=null)
is4URL = "http://" + System.getenv("IS4_HOSTNAME") + ":" + System.getenv("IS4_PORT");
}
public String description(){
return "SmapSourceHandler filter";
}
public synchronized void doFilter(HttpExchange exchange, Filter.Chain chain) throws IOException {
logger.fine("doFilter invoked");
boolean paramsOk = false;
if((paramsOk = parseParams(exchange)) && chain==null)
this.handle(exchange);
else if (!paramsOk)
sendResponse(exchange, 404, null);
else
chain.doFilter(exchange);
}
protected synchronized boolean parseParams(HttpExchange exchange) {
logger.info("Request URI: " + exchange.getRequestURI().toString());
StringTokenizer tokenizer = new StringTokenizer(exchange.getRequestURI().toString(), "?");
if(tokenizer != null && tokenizer.hasMoreTokens()){
String thisResourcePath = tokenizer.nextToken();
if(URI == null && !thisResourcePath.equals(URI) && !thisResourcePath.equals(URI + "/"))
return false;
if(tokenizer.countTokens()>0) {
StringTokenizer paramStrTokenizer = new StringTokenizer(tokenizer.nextToken(), "&");
if(paramStrTokenizer !=null && paramStrTokenizer.hasMoreTokens()){
while (paramStrTokenizer.hasMoreTokens()){
StringTokenizer paramPairsTokenizer = new StringTokenizer(paramStrTokenizer.nextToken(),"=");
if(paramPairsTokenizer != null && paramPairsTokenizer.hasMoreTokens()){
String attr = paramPairsTokenizer.nextToken();
String val = paramPairsTokenizer.nextToken();
exchange.setAttribute(attr, val);
logger.info("Added (" + attr + ", " + val + ") pair to exchange session");
}
}
}
} else{
logger.fine("Not enough tokens");
}
}
return true;
}
public synchronized void handle(HttpExchange exchange) throws IOException{
java.util.Date javaDate = new java.util.Date();
long ttime = javaDate.getTime();
Timestamp tstamp = new Timestamp(ttime);
logger.fine("putEntry: localtime=" + ttime);
String tstampStr = tstamp.toString();
/*logger.warning(tstampStr+ ": handle() called; "+ exchange.getRequestURI().toString()
+ "\nexchange: " + exchange + "\nthis: " + this);*/
String deploymentName = null;
String sensepoint = null;
String schema = null;
String templateAttr = null;
String formatStr = null;
//This addresses the HttpContext switch bug in the library
//The filter must be called BEFORE the handler
if (exchange.getHttpContext() != thisContext && exchange.getHttpContext().getFilters().size()==0) {
this.parseParams(exchange);
/*deploymentName = (String)exchange.getAttribute("deployment");
sensepoint = (String)exchange.getAttribute("sensepoint");
schema = (String)exchange.getAttribute("schema");
templateAttr = (String)exchange.getAttribute("template");
formatStr = (String)exchange.getAttribute("format");*/
thisContext = exchange.getHttpContext();
thisContext.getFilters().add(this);
}
try{
String requestMethod = exchange.getRequestMethod();
logger.info("Heard request Method= " + requestMethod);
if (requestMethod.equalsIgnoreCase("POST") ||
requestMethod.equalsIgnoreCase("PUT")) {
//Print out the request body
BufferedReader is = new BufferedReader(new InputStreamReader(exchange.getRequestBody()));
String line="";
StringBuffer bodyBuf = new StringBuffer();
while((line=is.readLine())!=null)
bodyBuf.append(line).append(" ");
//handle it
JSONObject report = (JSONObject) JSONSerializer.toJSON(bodyBuf.toString());
deploymentName = (String)exchange.getAttribute("deployment");
sensepoint = (String)exchange.getAttribute("sensepoint");
schema = (String)exchange.getAttribute("schema");
templateAttr = (String)exchange.getAttribute("template");
formatStr = (String)exchange.getAttribute("format");
marshallIs4Push(exchange, report, deploymentName, sensepoint, schema, templateAttr, formatStr);
}
} catch (JSONException e){
e.printStackTrace();
System.out.println("Response is NOT valid JSON");
}
}
protected synchronized void sendResponse(HttpExchange exchange, int errorCode, String response){
try{
logger.info("Sending Response");
Headers responseHeaders = exchange.getResponseHeaders();
responseHeaders.set("Content-Type", "application/json");
exchange.sendResponseHeaders(errorCode, 0);
OutputStream responseBody = exchange.getResponseBody();
if(response!=null)
responseBody.write(response.getBytes());
responseBody.close();
exchange.close();
}catch(Exception e){
logger.log(Level.WARNING, "Exception thrown while sending response",e);
exchange.close();
}
}
public synchronized void marshallIs4Push(HttpExchange exchange, JSONObject report,
String deploymentName, String sensepoint, String schema, String templateAddr,
String smapResource){
logger.info("marshalIs4Push: handling incoming report");
/*String deploymentName = null;
String sensepoint = null;
String schema = null;
String templateAddr = null;
String smapResource = null;*/
JSONObject response = new JSONObject();
JSONArray errors = new JSONArray();
/*if(exchange.getAttribute("deployment") != null
&& exchange.getAttribute("sensepoint")!=null && exchange.getAttribute("schema")!=null
&& exchange.getAttribute("format") != null)
{
deploymentName =(String)exchange.getAttribute("deployment");
sensepoint=(String)exchange.getAttribute("sensepoint");
schema=(String)exchange.getAttribute("schema");
templateAddr = (String)exchange.getAttribute("template");
smapResource = (String) exchange.getAttribute("format");*/
if(deploymentName != null && sensepoint != null && schema != null && templateAddr !=null
&& smapResource != null){
if(smapResource == null || !smapResource.endsWith("/formatting") || (new StringTokenizer(smapResource, "*")).countTokens() != 2){
errors.add("Invalid or missing 'format' parameter");
errors.add("Url format schema: http://[smap_resource]/data/*/<channel>/formatting");
response.put("errors", errors);
sendResponse(exchange, 200, response.toString());
return;
}
if(sensepoint.equalsIgnoreCase("all") && ((schema.equalsIgnoreCase("report"))|| schema.equalsIgnoreCase("reading"))) {
handleBulkReports(deploymentName,report,smapResource, templateAddr);
sendResponse(exchange, 200, null);
} else if(!sensepoint.equalsIgnoreCase("all") && (schema.equalsIgnoreCase("report") || schema.equalsIgnoreCase("reading"))) {
handleSingleReport(deploymentName,sensepoint,report,smapResource,templateAddr);
sendResponse(exchange, 200, null);
} else {
errors.add("Unrecognized request: " + exchange.getRequestURI().toString());
response.put("errors", errors);
sendResponse(exchange, 200, response.toString());
}
}else {
StringBuffer errorBuf = new StringBuffer();
errorBuf.append("marshallIs4Push: Could not handle incoming smap report; ");
errorBuf.append("\n\t\tAll URL parameters must be included: deployment=[name], sensepoint=[name|all], schema=[smap_schema], format=[smap_format_url]");
errorBuf.append("\n\t\tValid smap schema labels:");
errorBuf.append(" report, reading");
//logger.warning(errorBuf.toString());
errors.add(errorBuf.toString());
response.put("errors", errors);
sendResponse(exchange, 200, response.toString());
}
}
private String cleanFormatResource(String formatStr, String sensepoint){
String newFormatStr = null;
if(formatStr != null && !formatStr.equals("") && formatStr.endsWith("/formatting")) {
StringTokenizer formatTokens = new StringTokenizer(formatStr, "*");
if(formatTokens.countTokens() > 1)
newFormatStr = formatTokens.nextToken() + sensepoint + formatTokens.nextToken();
}
return newFormatStr;
}
private void storeFormatData(String formatUrl, String pubId){/*, String m1, String m2){
System.out.println(m1);
System.out.println(m2);*/
if(formatUrl != null){
try {
JSONObject formatObject = JSONSchemaValidator.fetchJSONObj(formatUrl);
if(formatObject != null){
formatObject.put("name", "formatting_entry");
formatObject.put("PubId", pubId);
formatObject.put("formatUrl", formatUrl);
DBAbstractionLayer.database.putEntry(formatObject);
}
} catch(Exception e){
logger.log(Level.WARNING, "", e);
}
}
}
private void handleBulkReports(String deploymentName, JSONObject report, String resource, String templateAddr){
Hashtable<String, String> nameToPubIdTable = null;
if(allDeployments.containsKey(deploymentName)) {
nameToPubIdTable = allDeployments.get(deploymentName);
} else {
nameToPubIdTable = new Hashtable<String, String>();
allDeployments.put(deploymentName, nameToPubIdTable);
/*if(pubid!=null && MySqlDriver.checkPubIdIsRegistered(pubid) && !nameToPubIdTable.containsKey(deploymentName+'_'+spid)){
}*/
}
String pubid = null;
Vector<String> sensepoints = new Vector<String>(report.keySet());
for(int i=0; i<sensepoints.size(); i++) {
String spid = sensepoints.get(i);
JSONObject thisDataObj = report.optJSONObject(spid);
boolean validObj = (thisDataObj != null && !thisDataObj.isNullObject());
//String message = "spid: " + spid + "\nreport: " + report.toString();
//message += "\nAssociate_object: " + thisDataObj + "valid? " + validObj + "\n";
//logger.warning(message);
String formatUrl = cleanFormatResource(resource, spid);
String dataResource = resource.replace("*", spid);
dataResource = resource.replace("/formatting", "");
if(nameToPubIdTable.containsKey(deploymentName+'_'+spid)){
pubid = nameToPubIdTable.get(deploymentName+'_'+spid);
if(validObj)
sendToIs4(pubid, thisDataObj);
} else if((pubid=sendJoin(deploymentName+'_'+spid, dataResource,templateAddr))==null || pubid.equals("") || pubid.equals("0")){
logger.warning("Could not add deployment " + deploymentName + ", sensepoint " + spid + " as a publisher");
} else{
nameToPubIdTable.put(deploymentName+'_'+spid, pubid);
if(validObj)
sendToIs4(pubid, thisDataObj);
}
if(validObj && pubid!=null){
//String m1 = "######### Fetched " + spid + " from report jsonobject:\nreport\n\n" + report.toString();
//String m2 = "######### ValidObject? " + validObj;
String message = "spid: " + spid + "\nreport: " + report.toString();
message += "\nAssociate_object: " + thisDataObj + "\nvalid? " + validObj + "\n";
message += "deployment=" + deploymentName + "\nresource=" + resource;
message += "\ntemplateAddr=" + templateAddr;
//logger.warning(message);
storeFormatData(formatUrl, pubid);//, m1, m2);
}
}
}
private void handleSingleReport(String deploymentName, String spid, JSONObject report, String resource, String templateAddr){
Hashtable<String, String> nameToPubIdTable = null;
if(allDeployments.containsKey(deploymentName)) {
nameToPubIdTable = allDeployments.get(deploymentName);
} else {
nameToPubIdTable = new Hashtable<String, String>();
allDeployments.put(deploymentName, nameToPubIdTable);
}
String pubid = null;
resource = resource.replace("/formatting", "");
if(nameToPubIdTable.containsKey(deploymentName+'_'+spid)){
pubid = nameToPubIdTable.get(deploymentName+'_'+spid);
sendToIs4(pubid, report);
} else if((pubid=sendJoin(deploymentName+'_'+spid, resource, templateAddr))==null || pubid.equals("") || pubid.equals("0")){
logger.warning("Could not add deployment " + deploymentName + ", sensepoint " + spid + " as a publisher");
} else{
nameToPubIdTable.put(deploymentName+'_'+spid, pubid);
sendToIs4(pubid, report);
}
storeFormatData(resource, pubid);
}
public String sendJoin(String devName, String resource, String templateAddr){
String regId = null;
if((regId = ((MySqlDriver)DBAbstractionLayer.database).getPid(devName)) != null)
return regId;
try
{
JSONObject joinReq = new JSONObject();
if(templateAddr != null) {
if(!templateAddr.startsWith("http://"))
templateAddr = "http://" + templateAddr;
joinReq = JSONSchemaValidator.fetchJSONObj(templateAddr); if(joinReq == null){
logger.warning("Invalid schema url: " + templateAddr);
return null;
}
} else {
joinReq = JSONSchemaValidator.fetchJSONObj("http://jortiz81.homelinux.com/is4/schemas/acme_join_request.json");
}
JSONObject objectStream = joinReq.getJSONObject("object_stream");
objectStream.put("device_name", devName);
joinReq.put("object_stream", objectStream);
if(resource != null){
JSONObject logicStream = joinReq.getJSONObject("logic_stream");
logicStream.put("resource", resource);
joinReq.put("logic_stream", logicStream);
}
String requestStr = joinReq.toString();
URL yahoo = new URL(is4URL + "/is4/pub/join");
URLConnection yc = yahoo.openConnection();
yc.setRequestProperty("Content-Type", "application/json");
yc.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(yc.getOutputStream());
wr.write(requestStr);
wr.flush();
//get response from IS4
BufferedReader in = new BufferedReader(new InputStreamReader(yc.getInputStream()));
String inputLine;
String wholeDoc = "";
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
wholeDoc += inputLine;
}
JSONObject is4Resp = (JSONObject) JSONSerializer.toJSON(wholeDoc);
if(is4Resp.getString("operation").equalsIgnoreCase("join") && is4Resp.getString("status").equalsIgnoreCase("success")){
regId = is4Resp.getString("ident");
UUID regIdUUID = UUID.fromString(regId);
}
in.close();
} catch(Exception e){
e.printStackTrace();
}
return regId;
}
public void sendToIs4(String pubid, JSONObject dataObj){
try {
if(dataObj != null && !dataObj.isNullObject()) {
URL pubRsrc = new URL(is4URL + "/is4/pub?schema=reading");
dataObj.put("PubId", pubid);
URLConnection urlConn = pubRsrc.openConnection();
urlConn.setRequestProperty("Content-Type", "application/json");
urlConn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(urlConn.getOutputStream());
wr.write(dataObj.toString());
wr.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
}
} catch (Exception e){
e.printStackTrace();
}
}
}