/*
* "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 2.0
*/
package local.rest.resources;
import local.db.*;
import local.rest.*;
import local.rest.resources.util.*;
import local.rest.interfaces.*;
import local.metadata.context.*;
import is4.*;
import is4.stats.SFSStatistics;
import net.sf.json.*;
import java.net.*;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.lang.StringBuffer;
import com.sun.net.httpserver.*;
import javax.naming.InvalidNameException;
import java.io.*;
public class Resource extends Filter implements HttpHandler, Serializable, Is4Resource{
protected static transient final Logger logger = Logger.getLogger(Resource.class.getPackage().getName());
protected String URI = null;
protected static HttpServer httpServer = null;
protected static MySqlDriver database = (MySqlDriver) DBAbstractionLayer.database;
protected static MongoDBDriver mongoDriver = new MongoDBDriver();
protected static SFSStatistics sfsStats = null;
//Query structures
protected JSONObject exchangeJSON = new JSONObject();
protected static final String[] props_ops = {"props_like", "props_and", "props_or", "props_not"};
protected static final String[] ts_ops = {"ts_gt", "ts_lt", "ts_ge", "ts_le"};
protected static Vector<String> propsOpsVec = new Vector<String>();
protected static Vector<String> tsOpsVec = new Vector<String>();
protected long last_props_ts = 0;
protected static MetadataGraph metadataGraph = null;
public int TYPE= ResourceUtils.DEFAULT_RSRC;
//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;
public Resource(String path) throws Exception, InvalidNameException {
resourceSetup(path);
if(sfsStats == null)
sfsStats = SFSStatistics.getInstance(mongoDriver.openConn());
}
public Resource(){
initQueryStructs();
}
public static void setMetadataGraph(MetadataGraph mgraph){
metadataGraph = mgraph;
}
public static void removeFromMetadataGraph(String uri){
metadataGraph.removeNode(uri);
}
protected void resourceSetup(String path) throws Exception, InvalidNameException{
if(path.indexOf("?") > 0 || path.indexOf("&") > 0)
throw new Exception("Invalid path string: No '&' or '?' allowed OR path already exists");
if(!path.endsWith("/"))
path += "/";
URI = path;
//save if already in database
if(!((MySqlDriver)DBAbstractionLayer.database).rrPathExists(path))
((MySqlDriver)DBAbstractionLayer.database).rrPutPath(URI);
//set last_props_ts
last_props_ts = database.getLastPropsTs(URI);
if(last_props_ts==0 && mongoDriver.getPropsHistCount(URI)>0){
logger.info("Fetching oldest properties values");
last_props_ts = mongoDriver.getMaxTsProps(URI);
JSONObject propsEntry = mongoDriver.getPropsEntry(URI, last_props_ts);
propsEntry.remove("_id");
propsEntry.remove("timestamp");
propsEntry.remove("is4_uri");
database.rrPutProperties(URI, propsEntry);
database.updateLastPropsTs(URI, last_props_ts);
}
initQueryStructs();
}
public void initQueryStructs(){
if(propsOpsVec.size()==0){
int i =0;
for(i=0; i<props_ops.length; i++)
propsOpsVec.addElement(props_ops[i]);
for(i=0; i<ts_ops.length; i++)
tsOpsVec.addElement(ts_ops[i]);
}
}
public String getURI(){
return URI;
}
public int getType(){
return TYPE;
}
//////////////// REST-accessible functions ////////////////
public void get(HttpExchange exchange, boolean internalCall, JSONObject internalResp){
try {
if(exchange.getAttribute("query") != null &&
((String)exchange.getAttribute("query")).equalsIgnoreCase("true") &&
exchange.getRequestURI().toString().contains("props_")){
logger.info("Handling PROPERTIES query");
query(exchange, null, internalCall, internalResp);
} else {
logger.fine("GETTING RESOURCES: " + URI);
JSONObject response = new JSONObject();
JSONArray subResourceNames = ((MySqlDriver)(DBAbstractionLayer.database)).rrGetChildren(URI);
logger.fine(subResourceNames.toString());
response.put("status", "success");
response.put("type", ResourceUtils.translateType(TYPE));
response.put("properties", database.rrGetProperties(URI));
findSymlinks(subResourceNames);
response.put("children",subResourceNames);
sendResponse(exchange, 200, response.toString(), internalCall, internalResp);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Error while responding to GET request",e);
} finally {
try {
if(exchange !=null){
exchange.getRequestBody().close();
exchange.getResponseBody().close();
exchange.close();
}
} catch(Exception e){
logger.log(Level.WARNING, "Trouble closing exchange in Resource", e);
}
}
}
public void put(HttpExchange exchange, String data, boolean internalCall, JSONObject internalResp){
try{
logger.info("PUT " + this.URI);
if(data != null){// && !isScriptRequest(exchange)){
JSONObject dataObj = (JSONObject) JSONSerializer.toJSON(data);
String op = dataObj.optString("operation");
logger.info("op=" + op);
String resourceName = dataObj.optString("resourceName");
if(op.equalsIgnoreCase("create_resource") && !resourceName.equals("") &&
ResourceUtils.putResource(this.URI, dataObj) ){
String myPath = this.URI;
if(!myPath.endsWith("/"))
myPath += "/";
metadataGraph.addNode(myPath + resourceName);
sendResponse(exchange, 201, null, internalCall, internalResp);
}
else if (op.equalsIgnoreCase("create_generic_publisher") &&
!dataObj.optString("resourceName").equals("")){
UUID newPubId = this.createPublisher(dataObj.optString("resourceName"));
if(newPubId != null && metadataGraph.addNode(this.URI + dataObj.optString("resourceName"))){
JSONObject response = new JSONObject();
response.put("status", "success");
response.put("is4_uri",
this.URI + dataObj.optString("resourceName"));
response.put("PubId", newPubId.toString());
sendResponse(exchange, 201, response.toString(), internalCall, internalResp);
} else {
sendResponse(exchange, 500, null, internalCall, internalResp);
}
}
else if(op.equalsIgnoreCase("create_symlink")) {
String name = dataObj.optString("name");
String is4url = dataObj.optString("url");
String localUri = dataObj.optString("uri");
if(!name.equals("") && !localUri.equals("") && RESTServer.isResource(localUri)){
SymlinkResource symlink = new SymlinkResource(URI + name + "/", localUri);
RESTServer.addResource(symlink);
//revert the change if the new addition introduces a cycle/loop in the metadata graph
JSONObject resp =new JSONObject();
JSONArray errors = new JSONArray();
JSONObject internal = new JSONObject();
if(!metadataGraph.addNode(symlink.getURI())){
symlink.loopDelete();
errors.add("possible cycle on adding: " + symlink.getURI());
resp.put("status", "fail");
resp.put("errors", errors);
sendResponse(exchange, 500, resp.toString(), internalCall, internalResp);
} else {
logger.info("added symlink: " + metadataGraph.addNode(URI + name + "/"));
sendResponse(exchange, 201, null, internalCall, internalResp);
}
}
else if(!name.equals("") && !is4url.equals("")){
try{
URL is4URL = new URL(is4url);
SymlinkResource symlink = new SymlinkResource(URI + name + "/", is4URL);
RESTServer.addResource(symlink);
logger.info("added symlink: " + metadataGraph.addNode(URI + name + "/"));
sendResponse(exchange, 201, null, internalCall, internalResp);
} catch(Exception e){
logger.log(Level.WARNING, "", e);
sendResponse(exchange, 400, "Invalid URL format", internalCall, internalResp);
}
}
else {
sendResponse(exchange, 400,
"Missing name, url, and/uri fields; Invalid request",
internalCall, internalResp);
}
}
else if( op.equalsIgnoreCase("create_smap_publisher")){
JSONArray errors = new JSONArray();
JSONObject newUris = new JSONObject();
JSONObject deviceRequest = new JSONObject();
//get device name
StringTokenizer tokenizer = new StringTokenizer(this.URI, "/");
int idx=0;
int numTokens = tokenizer.countTokens();
String devName = null;
while (idx != numTokens-1){
tokenizer.nextToken();
idx++;
}
devName = tokenizer.nextToken();
logger.info("operation: create_smap_publisher, devname="+ devName + " type=" + ResourceUtils.translateType(this.TYPE));
//populate the add-smap publisher request
boolean smapurlsSet = false;
deviceRequest.put("deviceName", devName);
if(dataObj.optJSONArray("smap_urls") != null){
deviceRequest.put("smap_urls", dataObj.optJSONArray("smap_urls"));
smapurlsSet = true;
} else if(!dataObj.optString("smap_url").equals("")){
deviceRequest.put("smap_url", dataObj.optString("smap_url"));
smapurlsSet = true;
}
if(!dataObj.optString("alias").equals("")){
deviceRequest.put("alias", dataObj.optString("alias"));
} else if(dataObj.optJSONArray("aliases") != null) {
deviceRequest.put("aliases", dataObj.optJSONArray("aliases"));
}
logger.info("Data Object:" + dataObj.toString());
logger.info("Device Request:" + deviceRequest.toString());
//check if smap urls are set
if(smapurlsSet){
DevicesResource.addSmapPublishersToDevice(this.URI, deviceRequest, errors, newUris);
} else {
errors.add("smap_url(s) not set in request");
}
//send response
JSONObject resp = new JSONObject();
if(errors.size()>0){
resp.put("status", "fail");
resp.put("errors", errors.toString());
sendResponse(exchange, 200, resp.toString(), internalCall, internalResp);
} else {
resp.put("status", "success");
resp.put("new_pubs", newUris);
sendResponse(exchange, 200, resp.toString(), internalCall, internalResp);
}
}
else {
sendResponse(exchange, 200, null, internalCall, internalResp);
}
return;
}
} catch (Exception e){
logger.log(Level.WARNING, "Request document not in proper JSON format", e);
sendResponse(exchange, 500, null, internalCall, internalResp);
return;
} finally {
try {
if(exchange != null){
exchange.getRequestBody().close();
exchange.getResponseBody().close();
exchange.close();
}
} catch(Exception e){
logger.log(Level.WARNING, "Trouble closing exchange in Resource", e);
}
}
//no content
sendResponse(exchange, 204, null, internalCall, internalResp);
}
private boolean isScriptRequest(HttpExchange exchange){
boolean scriptReq = false;
scriptReq = exchange.getAttribute("script") != null &&
((String) exchange.getAttribute("script")).equalsIgnoreCase("true");
if(scriptReq)
exchange.setAttribute("script", "");
return true;
}
public void post(HttpExchange exchange, String data, boolean internalCall, JSONObject internalResp){
logger.info("POST called: " + exchange.getRequestURI().toString());
try {
if(exchange.getAttribute("query") != null &&
((String)exchange.getAttribute("query")).equalsIgnoreCase("true") &&
exchange.getRequestURI().toString().contains("props_")){
logger.info("Handling PROPERTIES query");
query(exchange, data, internalCall, internalResp);
} else {
handlePropsReq(exchange, data, internalCall, internalResp);
//sendResponse(exchange, 200, null, internalCall, internalResp);
}
} catch(Exception e){
logger.log(Level.WARNING, "", e);
} finally {
try {
if(exchange != null){
exchange.getRequestBody().close();
exchange.getResponseBody().close();
exchange.close();
}
} catch(Exception e){
logger.log(Level.WARNING, "Trouble closing exchange in Resource", e);
}
}
}
public void delete(HttpExchange exchange, boolean internalCall, JSONObject internalResp){
try {
logger.info("Handling DELETE command for " + this.URI);
JSONArray children = database.rrGetChildren(this.URI);
if(children.size()==0){
//reset properties
JSONObject emptyProps = new JSONObject();
updateProperties(emptyProps);
//delete rest_resource entry
database.removeRestResource(this.URI);
RESTServer.removeResource(this);
//remove from internal graph
metadataGraph.removeNode(this.URI);
sendResponse(exchange, 200, null, internalCall, internalResp);
} else {
JSONObject response = new JSONObject();
JSONArray errors = new JSONArray();
response.put("status", "fail");
errors.add("Cannot delete resource with children");
response.put("errors", errors);
sendResponse(exchange, 200, response.toString(), internalCall, internalResp);
}
} catch(Exception e){
logger.log(Level.WARNING, "", e);
} finally {
try {
if(exchange != null){
exchange.getRequestBody().close();
exchange.getResponseBody().close();
exchange.close();
}
} catch(Exception e){
logger.log(Level.WARNING, "Trouble closing exchange in Resource", e);
}
}
}
//////////////// HttpHandler function implemention ////////////////
public synchronized void handle(HttpExchange exchange){
logger.info("exchange handler: " + exchange.getLocalAddress().getHostName() + ":" + exchange.getLocalAddress().getPort() + "->" +
exchange.getRemoteAddress());
try {
//get the uri and remove the parameters
String eUri = exchange.getRequestURI().toString();
if(eUri.contains("?"))
eUri = eUri.substring(0, eUri.indexOf("?"));
if(!eUri.contains("*")){
//does not have to be an exact match if the request is to a symlink resource
//otherwise the request MUST match the uri of this resource EXACTLY
logger.info("Invoked handle TYPE=" + ResourceUtils.translateType(TYPE));
if(TYPE != ResourceUtils.SYMLINK_RSRC){
String URI2 = null;
if(this.URI.endsWith("/"))
URI2 = this.URI.substring(0, this.URI.length()-1);
else
URI2 = this.URI + "/";
logger.info("URI=" + this.URI + "\nURI2=" + URI2 + "\nREQ_URI=" + exchange.getRequestURI());
String myUri = null;
if(eUri.contains("?")){
myUri = eUri.substring(0, eUri.indexOf("?"));
} else {
myUri = eUri;
}
if((!myUri.equalsIgnoreCase(this.URI) && !myUri.equalsIgnoreCase(URI2)) ||
!isActiveResource(this.URI)){
sendResponse(exchange, 404, null, false, null);
return;
}
}
//This address 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);
thisContext = exchange.getHttpContext();
thisContext.getFilters().add(this);
logger.warning("HttpContext switch bug in the httpserver library");
}
try {
String requestMethod = exchange.getRequestMethod();
if(requestMethod.equalsIgnoreCase("get")){
logger.info("handling GET");
sfsStats.incGet();
this.get(exchange, false, null);
return;
} else if (requestMethod.equalsIgnoreCase("put")){
logger.info("handling PUT");
sfsStats.incPut();
String obj = getPutPostData(exchange);
this.put(exchange, obj, false, null);
sfsStats.docReceived(obj);
return;
} else if (requestMethod.equalsIgnoreCase("post")) {
logger.info("handling POST");
sfsStats.incPost();
String obj = getPutPostData(exchange);
this.post(exchange, obj, false, null);
sfsStats.docReceived(obj);
return;
} else if (requestMethod.equalsIgnoreCase("delete")) {
logger.info("handling DELETE");
sfsStats.incDelete();
this.delete(exchange, false, null);
return;
}
} catch (Exception e){
logger.log(Level.WARNING, "Could not carry out the Filter operation", e);
}
} else {
//This address 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);
thisContext = exchange.getHttpContext();
thisContext.getFilters().add(this);
}
handleRecursiveFSQuery(exchange, false, null);//, myUri);
}
} catch(Exception e){
logger.log(Level.WARNING, "",e);
} finally {
try {
if(exchange !=null){
exchange.getRequestBody().close();
exchange.getResponseBody().close();
exchange.close();
}
} catch(Exception e){
logger.log(Level.WARNING, "Trouble closing exchange in Resource", e);
}
}
}
public String description(){
StringBuffer strBuf = new StringBuffer().append("Filter GET and POST requests for ").append(URI);
return strBuf.toString();
}
public synchronized void doFilter(HttpExchange exchange, Filter.Chain chain) throws IOException {
boolean paramsOk = false;
if((paramsOk = parseParams(exchange)) && chain==null)
this.handle(exchange);
else if (!paramsOk)
sendResponse(exchange, 404, null, false, null);
else
chain.doFilter(exchange);
}
protected synchronized boolean parseParams(HttpExchange exchange) {
logger.info("Request URI: " + exchange.getRequestURI().toString());
exchangeJSON.clear();
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);
exchangeJSON.put(attr, val);
logger.info("Added (" + attr + ", " + val + ") pair to exchange session");
}
}
}
} else{
logger.fine("Not enough tokens");
}
}
return true;
}
protected String getPutPostData(HttpExchange exchange){
try {
InputStream ist= exchange.getRequestBody();
InputStreamReader ir = new InputStreamReader(ist);
BufferedReader is = new BufferedReader(ir);
String line="";
StringBuffer bodyBuf = new StringBuffer();
while((line=is.readLine())!=null){
line = line.trim();
bodyBuf.append(line).append(" ");
}
//is.close();
return bodyBuf.toString();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception thrown while processing put/post data", e);
}
exchange= null;
return null;
}
public void sendResponse(HttpExchange exchange, int errorCode, String response, boolean internalCall, JSONObject internalResp){
OutputStream responseBody=null;
try{
if(internalCall){
copyResponse(response, internalResp);
return;
}
logger.info("Sending Response: " + response);
Headers responseHeaders = exchange.getResponseHeaders();
responseHeaders.set("Content-Type", "application/json");
responseHeaders.set("Connection", "close");
exchange.sendResponseHeaders(errorCode, 0);
responseBody = exchange.getResponseBody();
if(response!=null)
responseBody.write(response.getBytes());
responseBody.close();
sfsStats.docSent(response);
}catch(Exception e){
logger.log(Level.WARNING, "Exception thrown while sending response, closing exchange object",e);
} finally {
if(!internalCall){
try {
if(responseBody !=null){
responseBody.close();
logger.info("closing responseBody");
}
if(exchange !=null) {
exchange.getResponseBody().close();
logger.info("closing exchange: " + exchange.getLocalAddress().getHostName() + ":" + exchange.getLocalAddress().getPort() + "->" +
exchange.getRemoteAddress());
exchange.close();
}
exchange = null;
} catch(Exception e){
logger.log(Level.WARNING, "", e);
}
}
}
}
private void copyResponse(String response, JSONObject internalResp){
try{
if(internalResp != null){
if(response != null){
logger.fine("Copying response to internal buffer");
JSONObject respObj = (JSONObject) JSONSerializer.toJSON(response);
Iterator keys = respObj.keys();
while(keys.hasNext()){
String thisKey = (String) keys.next();
internalResp.put(thisKey, respObj.get(thisKey));
}
} else {
logger.fine("Response was null");
}
} else {
logger.fine("Internal buffer is null");
}
} catch(Exception e){
logger.log(Level.WARNING, "",e);
}
}
protected boolean isActiveResource(String uri){
int id = database.getRRTableId(uri);
if(id>=0)
return true;
return false;
}
public static void main(String[] args){
String clause1 = "nin:1,2,3";
String clause2 = "or:[smap:true|timestamp:gt:12]";
String clause3 = "gt:now,lt:now+2,gte:now-10";
String clause4 = "and:[smap:true|timestamp:12345]";
JSONObject gen1 = genJSONClause(clause1);
JSONObject gen2 = genJSONClause(clause2);
JSONObject gen3 = genJSONClause(clause3);
JSONObject gen4 = genJSONClause(clause4);
System.out.println("clause1: " + clause1 + "\tclause1JSON: " + gen1.toString());
System.out.println("clause2: " + clause2 + "\tclause2JSON: " + gen2.toString());
System.out.println("clause3: " + clause3 + "\tclause3JSON: " + gen3.toString());
System.out.println("clause4: " + clause4 + "\tclause4JSON: " + gen4.toString());
}
public static boolean isNumber(String val){
try {
Long.parseLong(val);
return true;
} catch(Exception e){
return false;
}
}
/**
* This function evaluates a clause string and returns a JSONObject that expresses
* the clause as a valid MongoDB query. The url query interface is meant to be a simple
* query interface for quickly obtaining values. If anything more sophististicated
* needs to be done, you may POST a query object following the mongodb query interface.
*
* ../query=true&props_val=<clause>&props=<clause>
*
* example queries:
* ..props_label=in:val1,val2,val3&props_timestamp=gt:12345,lt:23456
* ..props=or:[label:one|title:two]
*
* , is used to separate values in an $or, $and array condition or to separate
* conditions on a value.
* | is used between [] to separate conditional JSON objects
*
*/
public static JSONObject genJSONClause(String clause){
JSONObject clauseJSON = new JSONObject();
if(clause != null){
//case: ..&props=or:[label:one|title:two]
if(clause.startsWith("and:[") || clause.startsWith("or:[") &&
clause.endsWith("]")){
JSONArray vals = new JSONArray();
String valsStr = clause.substring(clause.indexOf("[")+1, clause.length()-1);
StringTokenizer valsToks = new StringTokenizer(valsStr, "|");
while(valsToks.hasMoreTokens()){
String thisToken = valsToks.nextToken();
StringTokenizer innerToks = new StringTokenizer(thisToken, ":");
if(innerToks.countTokens()==2){
String attr = innerToks.nextToken();
String valStr = innerToks.nextToken();
JSONObject cond = new JSONObject();
//convert the value to long if necessary
if(isNumber(valStr))
cond.put(attr, Long.parseLong(valStr));
else
cond.put(attr, valStr);
vals.add(cond);
} else{
//process inner clauses
JSONObject cond = new JSONObject();
String newToken = thisToken.substring(thisToken.indexOf(":")+1, thisToken.length());
JSONObject innerCond = genJSONClause(newToken);
if(innerCond != null){
cond.put(innerToks.nextToken(), innerCond);
vals.add(cond);
}
}
}
String op = "$" + clause.substring(0, clause.indexOf(":"));
clauseJSON.put(op, vals);
return clauseJSON;
}
//case: ..&props_label=in:val1,val2,val3
else if(clause.startsWith("in:") || clause.startsWith("nin:") ||
clause.startsWith("and:") || clause.startsWith("or:")){
JSONArray vals = new JSONArray();
String valsStr = clause.substring(clause.indexOf(":")+1, clause.length());
StringTokenizer valsToks = new StringTokenizer(valsStr, ",");
while(valsToks.hasMoreTokens()){
String valStr = valsToks.nextToken();
if(isNumber(valStr))
vals.add(Long.parseLong(valStr));
else
vals.add(valStr);
}
String op = "$" + clause.substring(0, clause.indexOf(":"));
clauseJSON.put(op, vals);
return clauseJSON;
}
//case: ..&props_timestamp=gt:12345,lt:23456
else if(clause.startsWith("gt:") || clause.startsWith("lt:") ||
clause.startsWith("gte:") || clause.startsWith("lte:") ||
clause.startsWith("ne:") ){
StringTokenizer valToks = new StringTokenizer(clause, ",");
while(valToks.hasMoreTokens()){
String thisToken = valToks.nextToken();
StringTokenizer innerToks = new StringTokenizer(thisToken, ":");
if(innerToks.countTokens()==2){
String op = innerToks.nextToken();
String valStr = innerToks.nextToken();
long valLong = -1;
if(valStr.contains("now")){
Date date = new Date();
long timestamp = date.getTime()/1000;
if(valStr.equalsIgnoreCase("now")){
valLong = timestamp;
} else if(valStr.startsWith("now-")){
String numStr = valStr.substring(valStr.indexOf("-")+1, valStr.length());
if(isNumber(numStr)){
long sub = Long.parseLong(numStr);
valLong = timestamp - sub;
//System.out.println("parsedNum: " + num + "; " + val + "=" + val);
}
} else if(valStr.startsWith("now+")){
String numStr = valStr.substring(valStr.indexOf("+")+1, valStr.length());
if(isNumber(numStr)){
long add = Long.parseLong(numStr);
valLong = timestamp + add;
//System.out.println("parsedNum: " + num + "; " + val + "=" + val);
}
}
} else if(isNumber(valStr)){
valLong = Long.parseLong(valStr);
}
if(valLong!=-1)
clauseJSON.put("$" + op, valLong);
else
clauseJSON.put("$" + op, valStr);
}
}
return clauseJSON;
}
}
return null;
}
//evaluate query
public void query(HttpExchange exchange, String data, boolean internalCall, JSONObject internalResp){
JSONObject resp = new JSONObject();
JSONArray errors = new JSONArray();
try{
JSONObject propsQueryObj = new JSONObject();
//get query object from input data
if(data != null && !data.equals("")){
JSONObject dataJsonObj = (JSONObject) JSONSerializer.toJSON(data);
if(TYPE == ResourceUtils.PUBLISHER_RSRC || TYPE == ResourceUtils.GENERIC_PUBLISHER_RSRC){
JSONObject dataPropsQuery = dataJsonObj.optJSONObject("props_query");
propsQueryObj.putAll(dataPropsQuery);
} else {
propsQueryObj.putAll(dataJsonObj);
}
}
Iterator keys = exchangeJSON.keys();
Vector<String> attributes = new Vector<String>();
Vector<String> values = new Vector<String>();
while(keys.hasNext()){
String thisKey = (String) keys.next();
logger.fine("Keys found!; thisKey=" + thisKey);
if(thisKey.startsWith("props_")){
String str = "props_";
String queryKey = thisKey.substring(thisKey.indexOf(str)+str.length(), thisKey.length());
String queryValue = exchangeJSON.optString(thisKey);
logger.info("Query Value: " + queryValue);
JSONObject conditions = Resource.genJSONClause(queryValue);
logger.info("Conditions: " + conditions);
if(conditions!=null)
propsQueryObj.put(queryKey, conditions);
else
propsQueryObj.put(queryKey, queryValue);
} else if(thisKey.startsWith("props")){
String queryValue = exchangeJSON.optString(thisKey);
JSONObject conditions = Resource.genJSONClause(queryValue);
if(conditions!=null)
propsQueryObj.putAll(conditions);
else
logger.warning("Invalid conditions set for generic props query");
}
}
logger.fine("Props Query: " + propsQueryObj.toString());
if(!propsQueryObj.toString().equals("{}")){
propsQueryObj.put("is4_uri", URI);
if(last_props_ts>0)
propsQueryObj.put("timestamp", last_props_ts);
logger.info("Props Query: " + propsQueryObj.toString());
JSONObject mqResp = mongoDriver.queryProps(propsQueryObj.toString());
logger.fine("mqResp: " + mqResp.toString());
JSONArray propsRespObjArray = mqResp.getJSONArray("results");
if(propsRespObjArray.size()>0){
JSONObject propsRespObj = (JSONObject) propsRespObjArray.get(0);
propsRespObj.remove("is4_uri");
propsRespObj.remove("timestamp");
mqResp.put("results", propsRespObj);
resp.putAll(mqResp);
}
} else {
errors.add("Empty or invalid query");
logger.warning(errors.toString());
resp.put("errors", errors);
}
} catch (Exception e){
logger.log(Level.WARNING, "", e);
if(e instanceof JSONException){
errors.add("Invalid JSON for POST data; url params ignored");
resp.put(errors, errors);
sendResponse(exchange, 200, resp.toString(), internalCall, internalResp);
return;
}
}
sendResponse(exchange, 200, resp.toString(), internalCall, internalResp);
}
public UUID createPublisher(String pubName){
JSONArray children = database.rrGetChildren(this.URI);
try {
UUID thisPubIdUUID = null;
String pubUri = this.URI + pubName;
if(!children.contains(pubName) &&
(thisPubIdUUID=database.isPublisher(pubUri, false)) == null){
//register the publisher
Registrar registrar = Registrar.registrarInstance();
String newPubId = registrar.registerDevice(pubUri);
try{
thisPubIdUUID = UUID.fromString(newPubId);
} catch(Exception e){
logger.log(Level.WARNING, "", e);
}
//add to publishers table
database.addPublisher(thisPubIdUUID, null, null, pubUri, null);
GenericPublisherResource p = new GenericPublisherResource(pubUri, thisPubIdUUID);
RESTServer.addResource(p);
return thisPubIdUUID;
} else {
logger.warning("Publisher already registered or publisher name already "+
"child of " + this.URI + "\nPubName = " + pubName +
"\n\tChildren: " + children.toString() );
}
//bind the device to this context
//database.addDeviceEntry(deviceName, this.URI, thisPubIdUUID);
} catch (Exception e){
logger.log(Level.WARNING, "",e);
}
return null;
}
protected void setExchangeJSON(JSONObject params){
exchangeJSON.putAll(params);
}
/**
* Resolves the uri. If query has been posted, it is applied to each resource that the uri
* solves to. The results are returns in the following format.
*
* {
* "/is4/...":{..}
* }
*
* The attribute is the uri, the value is the results of applying the query to that uri.
*/
protected void handleRecursiveFSQuery(HttpExchange exchange, boolean internalCall, JSONObject internalResp){//, String uri){
String requestUri= null;
if(internalCall && exchange.getAttribute("request_uri") != null &&
!((String) exchange.getAttribute("request_uri")).equals(""))
{
requestUri = (String)exchange.getAttribute("request_uri");
exchange.setAttribute("request_uri", "");
}
else{
requestUri = exchange.getRequestURI().toString();
}
logger.info("FSQuery:: requestUri=" + requestUri + " internalCall: " + internalCall);
//logger.info("FSQuery Uri: " + uri + " Request URI: " + requestUri);
JSONArray resolvedUris = new JSONArray();
String uriOnly = requestUri;
if(requestUri.contains("?")){
uriOnly = requestUri.substring(0, uriOnly.indexOf("?"));
logger.info("Request URI, no params: " + uriOnly + " \ttype=" + (String) exchange.getAttribute("type"));
resolvedUris.addAll( database.resolveStarredUri(uriOnly, (String) exchange.getAttribute("type")) );
} else{
resolvedUris.addAll( database.resolveStarredUri(uriOnly, null) );
}
logger.info("Resolved Uris: " + resolvedUris.toString());
//get the request method
String requestMethod = exchange.getRequestMethod();
String putPostData = null;
if(requestMethod.equalsIgnoreCase("put") || requestMethod.equalsIgnoreCase("post"))
putPostData = getPutPostData(exchange);
JSONObject responses = new JSONObject();
try{
if(requestMethod.equalsIgnoreCase("get")){
for(int i=0; i<resolvedUris.size(); i++){
Resource thisResource = RESTServer.getResource(resolvedUris.getString(i));
if(thisResource != null){
JSONObject respBuffer = new JSONObject();
thisResource.setExchangeJSON(this.exchangeJSON);
thisResource.get(exchange, true, respBuffer);
if(thisResource.TYPE == ResourceUtils.SYMLINK_RSRC){
processResponse(thisResource, respBuffer);
responses.putAll(respBuffer);
} else {
responses.put(thisResource.URI, respBuffer);
}
}
}
} else if(requestMethod.equalsIgnoreCase("put")){
for(int i=0; i<resolvedUris.size(); i++){
Resource thisResource = RESTServer.getResource(resolvedUris.getString(i));
if(thisResource != null){
JSONObject respBuffer = new JSONObject();
thisResource.setExchangeJSON(this.exchangeJSON);
thisResource.put(exchange, putPostData, true, respBuffer);
if(thisResource.TYPE == ResourceUtils.SYMLINK_RSRC){
processResponse(thisResource, respBuffer);
responses.putAll(respBuffer);
} else{
responses.put(thisResource.URI, respBuffer);
}
}
}
} else if(requestMethod.equalsIgnoreCase("post")){
for(int i=0; i<resolvedUris.size(); i++){
Resource thisResource = RESTServer.getResource(resolvedUris.getString(i));
if(thisResource != null){
JSONObject respBuffer = new JSONObject();
thisResource.setExchangeJSON(this.exchangeJSON);
thisResource.post(exchange, putPostData, true, respBuffer);
if(thisResource.TYPE == ResourceUtils.SYMLINK_RSRC){
processResponse(thisResource, respBuffer);
responses.putAll(respBuffer);
} else {
responses.put(thisResource.URI, respBuffer);
}
}
}
} else if(requestMethod.equalsIgnoreCase("delete")){
if(resolvedUris.size()>0)
depthFirstSort(resolvedUris);
for(int i=0; i<resolvedUris.size(); i++){
Resource thisResource = RESTServer.getResource(resolvedUris.getString(i));
if(thisResource != null){
JSONObject respBuffer = new JSONObject();
thisResource.setExchangeJSON(this.exchangeJSON);
thisResource.delete(exchange, true, respBuffer);
if(thisResource.TYPE == ResourceUtils.SYMLINK_RSRC){
processResponse(thisResource, respBuffer);
responses.putAll(respBuffer);
} else {
responses.put(thisResource.URI, respBuffer);
}
}
}
}
} catch (Exception e){
logger.log(Level.WARNING, "", e);
}
sendResponse(exchange, 200, responses.toString(), internalCall, internalResp);
}
private void processResponse(Resource resource, JSONObject buffer){
if(resource != null && buffer != null && resource.TYPE == ResourceUtils.SYMLINK_RSRC){
Iterator keys = buffer.keys();
Vector<String> oriKeys = new Vector<String>();
StringBuffer allKeysStrBuf = new StringBuffer();
while(keys.hasNext()){
String thisKey = (String) keys.next();
oriKeys.add(thisKey);
allKeysStrBuf.append(thisKey + "\n");
}
logger.info("\n" + allKeysStrBuf.toString());
String slinkUri = resource.getURI();
String linksToUril = ((SymlinkResource)resource).getLinkString();
logger.info("slink uri: " + slinkUri + " links to: " + linksToUril);
if(linksToUril.startsWith("http")){
try{
URL url = new URL(linksToUril);
linksToUril = url.getPath();
logger.info("Now links to: " + linksToUril);
} catch(Exception e){}
}
for(int i=0; i<oriKeys.size(); i++){
String thisKey = oriKeys.elementAt(i);
logger.info("Old key: " + thisKey);
JSONObject thisResp = buffer.getJSONObject(thisKey);
buffer.remove(thisKey);
thisKey = thisKey.replace(linksToUril, slinkUri);
logger.info("New key: " + thisKey);
thisKey = thisKey.replaceAll("\\/+", "/");
buffer.put(thisKey, thisResp);
}
}
}
private void depthFirstSort(JSONArray uris){
JSONObject buckets = new JSONObject();
Vector<Integer> keysVec = new Vector<Integer>();
for(int i=0; i< uris.size(); i++){
String thisUri = uris.getString(i);
logger.info("thisUri: " + thisUri);
StringTokenizer tokenizer = new StringTokenizer(thisUri, "/");
Integer tokenCount = new Integer(tokenizer.countTokens());
JSONArray thisBucket = buckets.optJSONArray(tokenCount.toString());
if(thisBucket == null){
thisBucket = new JSONArray();
keysVec.add(tokenCount);
}
thisBucket.add(thisUri);
buckets.put(tokenCount.toString(), thisBucket);
}
Integer[] bucketSizes = new Integer[keysVec.size()];
keysVec.toArray(bucketSizes);
Arrays.sort(bucketSizes);
uris.clear();
for(int i=keysVec.size()-1; i>=0; i--){
logger.info("Sorted list: " + bucketSizes[i] + "\nKey Vec: " + keysVec.get(i));
JSONArray bucket = buckets.getJSONArray(bucketSizes[i].toString());
for(int j=0; j<bucket.size(); j++)
uris.addAll(bucket);
}
}
public void updateProperties(JSONObject propsObj){
MongoDBDriver mongoDriver = new MongoDBDriver();
//add is4_uri
propsObj.put("is4_uri", URI.toString());
//add timestamp
Date date = new Date();
long timestamp = date.getTime()/1000;
propsObj.put("timestamp", timestamp);
//store in mongodb repos
mongoDriver.putPropsEntry(propsObj);
//save the last updated timestamp in the database
database.updateLastPropsTs(URI, timestamp);
last_props_ts = timestamp;
//place it in buffer
/*if(database.hasPropertiesBuffered(URI))
database.insertPropertiesIntoBuffer(URI, propsObj);
else
database.updatePropertiesInBuffer(URI, propsObj);*/
}
public void handlePropsReq(HttpExchange exchange, String data, boolean internalCall, JSONObject internalResp){
try{
JSONObject dataObj = (JSONObject) JSONSerializer.toJSON(data);
String op = dataObj.getString("operation");
if (op.equalsIgnoreCase("update_properties")){
logger.info("processing update_properties");
JSONObject response = new JSONObject();
JSONObject currentProps = database.rrGetProperties(URI);
JSONObject properties = null;
try{
properties = dataObj.getJSONObject("properties");
} catch(Exception e){
logger.log(Level.WARNING, "", e);
response.put("status", "fail");
JSONArray errors = new JSONArray();
errors.add("Missing properties object");
response.put("errors", errors);
sendResponse(exchange, 200, response.toString(), internalCall, internalResp);
return;
}
if(currentProps != null){
Iterator keys = properties.keys();
while(keys.hasNext()){
String thisKey = (String) keys.next();
currentProps.put(thisKey, properties.get(thisKey));
}
database.rrPutProperties(URI, currentProps);
updateProperties(currentProps);
} else {
database.rrPutProperties(URI, properties);
updateProperties(properties);
}
response.put("status", "success");
sendResponse(exchange, 200, response.toString(), internalCall, internalResp);
}
else if (op.equalsIgnoreCase("overwrite_properties")){
logger.info("processing overwrite_properties");
JSONObject response = new JSONObject();
JSONObject properties = null;
try{
properties = dataObj.getJSONObject("properties");
} catch(Exception e){
logger.log(Level.WARNING, "", e);
response.put("status", "fail");
JSONArray errors = new JSONArray();
errors.add("Missing properties object");
response.put("errors", errors);
sendResponse(exchange, 200, response.toString(), internalCall, internalResp);
}
database.rrPutProperties(URI, properties);
updateProperties(properties);
response.put("status", "success");
sendResponse(exchange, 200, response.toString(), internalCall, internalResp);
}
else{
sendResponse(exchange, 200, null, internalCall, internalResp);
}
} catch (Exception e){
//silent fail
sendResponse(exchange, 200, null, internalCall, internalResp);
}
}
private void findSymlinks(JSONArray children){
JSONArray newChildren = new JSONArray();
for(int i=0; i<children.size(); i++){
String childName = (String) children.get(i);
String resourcePath = URI + childName;
boolean isSymlink = database.isSymlink(resourcePath);
if(isSymlink){
logger.info(resourcePath + " is a symlink");
SymlinkResource slr = (SymlinkResource) RESTServer.getResource(resourcePath);
if(slr != null){
String symlinkStr = childName + " -> " + slr.getLinkString();
newChildren.add(symlinkStr);
}
} else {
newChildren.add(childName);
}
}
children.clear();
children.addAll(newChildren);
}
private Vector<String> createTokenVector(String target, String delim){
StringTokenizer tokens = new StringTokenizer(target, delim);
Vector<String> allToks = new Vector<String>();
while(tokens.hasMoreTokens())
allToks.add((String)tokens.nextToken());
return allToks;
}
}