/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2016, Ecole Polytechnique Federale de Lausanne (EPFL)
*
* This file is part of GSN.
*
* GSN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GSN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GSN. If not, see <http://www.gnu.org/licenses/>.
*
* File: src/ch/epfl/gsn/wrappers/general/RemoteRestAPIWrapper.java
*
* @author Julien Eberle
*
*/
package ch.epfl.gsn.wrappers.general;
import play.libs.Json;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
import org.apache.commons.codec.binary.Base64;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import ch.epfl.gsn.beans.AddressBean;
import ch.epfl.gsn.beans.DataField;
import ch.epfl.gsn.beans.StreamElement;
import ch.epfl.gsn.wrappers.AbstractWrapper;
import org.slf4j.Logger;
public class RemoteRestAPIWrapper extends AbstractWrapper {
private final transient Logger logger = LoggerFactory.getLogger(RemoteRestAPIWrapper.class);
private DataField[] structure = null;
private String wsURL;
String clientId;
String clientSecret;
private AddressBean addressBean;
private long lastReceivedTimestamp = -1;
private String token = "";
private String vsName;
private HttpClient client = HttpClients.createDefault();
private HttpGet getData = null;
private int samplingPeriodInMsc;
public DataField[] getOutputFormat() {
return structure;
}
public String getWrapperName() {
return "Remote REST API Wrapper";
}
public boolean initialize() {
try {
addressBean = getActiveAddressBean( );
wsURL = addressBean.getPredicateValue("url");
if ( wsURL == null || wsURL.trim().length() == 0 ) {
logger.warn( "The url parameter is missing from the Remote Rest API wrapper, initialization failed." );
return false;
}
clientId = addressBean.getPredicateValue("client_id");
if ( clientId == null || clientId.trim().length() == 0 ) {
logger.warn( "The client_id parameter is missing from the Remote Rest API wrapper, initialization failed." );
return false;
}
clientSecret = addressBean.getPredicateValue("client_secret");
if ( clientSecret == null || clientSecret.trim().length() == 0 ) {
logger.warn( "The client_secret parameter is missing from the Remote Rest API wrapper, initialization failed." );
return false;
}
vsName = addressBean.getPredicateValue("vs_name");
if ( vsName == null || vsName.trim().length() == 0 ) {
logger.warn( "The vs_name parameter is missing from the Remote Rest API wrapper, initialization failed." );
return false;
}
String starting = addressBean.getPredicateValueWithDefault("starting_time", ""+System.currentTimeMillis());
lastReceivedTimestamp = Long.valueOf(starting);
samplingPeriodInMsc = addressBean.getPredicateValueAsInt("sampling", 10000);
token = getToken();
structure = getRemoteStructure();
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
return structure != null;
}
public String getToken(){
HttpPost post = new HttpPost(wsURL + "/oauth2/token");
List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>();
parametersBody.add(new BasicNameValuePair("grant_type", "client_credentials"));
parametersBody.add(new BasicNameValuePair("client_id", clientId));
parametersBody.add(new BasicNameValuePair("client_secret", clientSecret));
HttpResponse response = null;
try {
post.setEntity(new UrlEncodedFormEntity(parametersBody, StandardCharsets.UTF_8));
response = client.execute(post);
int code = response.getStatusLine().getStatusCode();
if (code == 401) {
// Add Basic Authorization header
post.addHeader("Authorization",Base64.encodeBase64String((clientId+":"+clientSecret).getBytes()));
post.releaseConnection();
response = client.execute(post);
code = response.getStatusLine().getStatusCode();
}
if (code == 200){
String content = EntityUtils.toString(response.getEntity());
return Json.parse(content).get("access_token").asText();
}
} catch (Exception e){
logger.error(e.getMessage(), e);
}
return null;
}
private String doRequest(HttpGet get){
HttpResponse response = null;
int code = 401;
try {
if (token != null){
get.removeHeaders("Authorization");
get.addHeader("Authorization", "Bearer " + token);
get.releaseConnection();
response = client.execute(get);
code = response.getStatusLine().getStatusCode();
}
if (code == 401) {
// Access token is invalid or expired. Regenerate the access token
getToken();
if (token != null) {
get.removeHeaders("Authorization");
get.addHeader("Authorization", "Bearer " + token);
get.releaseConnection();
response = client.execute(get);
code = response.getStatusLine().getStatusCode();
}
}
if (code == 200){
return EntityUtils.toString(response.getEntity());
}
} catch (Exception e){
logger.error(e.getMessage(), e);
}
return "";
}
public DataField[] getRemoteStructure() throws IOException, ClassNotFoundException {
HttpGet get = new HttpGet(wsURL + "/api/sensors/" + vsName);
try {
String content = doRequest(get);
JsonNode jn = Json.parse(content).get("properties");
DataField[] df = new DataField[jn.get("fields").size()-1];
int i = 0;
for(JsonNode f : jn.get("fields")){
if (f.get("name").asText().equals("timestamp")) continue;
df[i] = new DataField(f.get("name").asText(),f.get("type").asText());
i++;
}
return df;
} catch (Exception e){
logger.error(e.getMessage(), e);
}
return null;
}
public void dispose() {
try {
getData.releaseConnection();
} catch (Exception e) {
logger.debug(e.getMessage(), e);
}
}
public void run() {
getData = new HttpGet();
while (isActive()) {
String uri = wsURL + "/api/sensors/" + vsName + "/data?from=" + ISODateTimeFormat.dateTime().print(lastReceivedTimestamp);
try {
getData.setURI(new URI(uri));
String content = doRequest(getData);
for (StreamElement se : StreamElement.fromJSON(content)){
manualDataInsertion(se);
}
Thread.sleep(samplingPeriodInMsc);
}
catch (Exception e) {
logger.warn("Something went wrong when querying the REST API at " + uri + " trying again in 1 minute...");
try {
if (isActive()) {
Thread.sleep(60000);
}
} catch (Exception err) {
logger.debug(err.getMessage(), err);
}
}
}
}
public boolean manualDataInsertion(StreamElement se) {
try {
// If the stream element is out of order, we accept the stream element and wait for the next (update the last received time and return true)
if (isOutOfOrder(se)) {
lastReceivedTimestamp = se.getTimeStamp();
return true;
}
// Otherwise, we first try to insert the stream element.
// If the stream element was inserted succesfully, we wait for the next,
// otherwise, we return false.
boolean status = postStreamElement(se);
if (status)
lastReceivedTimestamp = se.getTimeStamp();
return status;
}
catch (SQLException e) {
logger.warn(e.getMessage(), e);
return false;
}
}
}