/*
* Copyright 2005-2015 WSO2, Inc. (http://wso2.com)
*
* 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 org.wso2.carbon.bpmn.extensions.rest;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.wso2.carbon.bpmn.core.BPMNConstants;
import org.wso2.carbon.bpmn.core.types.datatypes.json.api.JsonNodeObject;
import org.wso2.carbon.utils.CarbonUtils;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
/**
* Utility class for invoking HTTP endpoints.
*/
public class RESTInvoker {
private static final Log log = LogFactory.getLog(RESTInvoker.class);
private int maxTotalConnections;
private int maxTotalConnectionsPerRoute;
private int connectionTimeout;
private int socketTimeout;
private CloseableHttpClient client = null;
private PoolingHttpClientConnectionManager connectionManager = null;
{
maxTotalConnectionsPerRoute = RESTConstants.MAX_TOTAL_CONNECTIONS_PER_ROUTE;
maxTotalConnections = RESTConstants.MAX_TOTAL_CONNECTIONS;
connectionTimeout = RESTConstants.CONNECTION_TIMEOUT;
socketTimeout = RESTConstants.SOCKET_TIMEOUT;
}
public RESTInvoker() {
configureHttpClient();
}
private void parseConfiguration() {
String carbonConfigDirPath = CarbonUtils.getCarbonConfigDirPath();
String activitiConfigPath = carbonConfigDirPath + File.separator +
BPMNConstants.ACTIVITI_CONFIGURATION_FILE_NAME;
File configFile = new File(activitiConfigPath);
try {
String configContent = FileUtils.readFileToString(configFile);
OMElement configElement = AXIOMUtil.stringToOM(configContent);
Iterator beans = configElement.getChildrenWithName(
new QName("http://www.springframework.org/schema/beans", "bean"));
while (beans.hasNext()) {
OMElement bean = (OMElement) beans.next();
String beanId = bean.getAttributeValue(new QName(null, "id"));
if (beanId.equals(RESTConstants.REST_CLIENT_CONFIG_ELEMENT)) {
Iterator beanProps = bean.getChildrenWithName(
new QName("http://www.springframework.org/schema/beans", "property"));
while (beanProps.hasNext()) {
OMElement beanProp = (OMElement) beanProps.next();
String beanName = beanProp.getAttributeValue(new QName(null, "name"));
if (RESTConstants.REST_CLIENT_MAX_TOTAL_CONNECTIONS.equals(beanName)) {
String value = beanProp.getAttributeValue(new QName(null, "value"));
if (value != null && !value.trim().equals("")) {
maxTotalConnections = Integer.parseInt(value);
}
if (log.isDebugEnabled()) {
log.debug("Max total http connections " + maxTotalConnections);
}
} else if (RESTConstants.REST_CLIENT_MAX_CONNECTIONS_PER_ROUTE.equals(beanName)) {
String value = beanProp.getAttributeValue(new QName(null, "value"));
if (value != null && !value.trim().equals("")) {
maxTotalConnectionsPerRoute = Integer.parseInt(value);
}
if (log.isDebugEnabled()) {
log.debug("Max total client connections per route " + maxTotalConnectionsPerRoute);
}
} else if (RESTConstants.REST_CLEINT_CONNECTION_TIMEOUT.equals(beanName)) {
String value = beanProp.getAttributeValue(new QName(null, "value"));
if (value != null && !value.trim().equals("")) {
connectionTimeout = Integer.parseInt(value);
}
} else if (RESTConstants.REST_CLEINT_SOCKET_TIMEOUT.equals(beanName)) {
String value = beanProp.getAttributeValue(new QName(null, "value"));
if (value != null && !value.trim().equals("")) {
socketTimeout = Integer.parseInt(value);
}
}
}
}
}
} catch (IOException | XMLStreamException e) {
log.error("Error in processing http connection settings, using default settings", e);
}
}
private void configureHttpClient() {
parseConfiguration();
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setExpectContinueEnabled(true)
.setConnectTimeout(connectionTimeout)
.setSocketTimeout(socketTimeout)
.build();
connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setDefaultMaxPerRoute(maxTotalConnectionsPerRoute);
connectionManager.setMaxTotal(maxTotalConnections);
client = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(defaultRequestConfig)
.build();
if (log.isDebugEnabled()) {
log.debug("BPMN REST client initialized with" +
"maxTotalConnection = " + maxTotalConnections +
"maxConnectionsPerRoute = " + maxTotalConnectionsPerRoute +
"connectionTimeout = " + connectionTimeout);
}
}
public void closeHttpClient() {
IOUtils.closeQuietly(client);
IOUtils.closeQuietly(connectionManager);
}
private CloseableHttpResponse sendReceiveRequest(HttpRequestBase requestBase, String username,
String password) throws IOException {
CloseableHttpResponse response;
if (username != null && !username.equals("") && password != null) {
String combinedCredentials = username + ":" + password;
byte[] encodedCredentials = Base64.encodeBase64(combinedCredentials.getBytes(StandardCharsets.UTF_8));
requestBase.addHeader("Authorization", "Basic " + new String(encodedCredentials));
response = client.execute(requestBase);
} else {
response = client.execute(requestBase);
}
return response;
}
private void processHeaderList(HttpRequestBase request, JsonNodeObject jsonHeaders) {
if (jsonHeaders != null) {
Iterator<String> iterator = jsonHeaders.fieldNames();
while (iterator.hasNext()) {
String key = iterator.next();
String value = jsonHeaders.findValue(key).textValue();
request.addHeader(key, value);
}
}
}
/**
* Invokes the http GET method
*
* @param uri endpoint/service url
* @param jsonHeaders header list
* @param username username for authentication
* @param password password for authentication
* @return RESTResponse of the GET request (can be the response body or the response status code)
* @throws Exception
*/
public RESTResponse invokeGET(URI uri, JsonNodeObject jsonHeaders, String username, String password) throws IOException {
HttpGet httpGet = null;
CloseableHttpResponse response = null;
Header[] headers;
int httpStatus;
String contentType;
String output;
try {
httpGet = new HttpGet(uri);
processHeaderList(httpGet, jsonHeaders);
response = sendReceiveRequest(httpGet, username, password);
output = IOUtils.toString(response.getEntity().getContent());
headers = response.getAllHeaders();
httpStatus = response.getStatusLine().getStatusCode();
contentType = response.getEntity().getContentType().getValue();
if (log.isTraceEnabled()) {
log.trace("Invoked GET " + uri.toString() + " - Response message: " + output);
}
EntityUtils.consume(response.getEntity());
} finally {
if (response != null) {
IOUtils.closeQuietly(response);
}
if (httpGet != null) {
httpGet.releaseConnection();
}
}
return new RESTResponse(contentType, output, headers, httpStatus);
}
/**
* Invokes the http POST method
*
* @param uri endpoint/service url
* @param jsonHeaders header list
* @param username username for authentication
* @param password password for authentication
* @param payload payload body passed
* @return RESTResponse of the POST request (can be the response body or the response status code)
* @throws Exception
*/
public RESTResponse invokePOST(URI uri, JsonNodeObject jsonHeaders, String username, String password, String payload) throws IOException {
HttpPost httpPost = null;
CloseableHttpResponse response = null;
Header[] headers;
int httpStatus;
String contentType;
String output;
try {
httpPost = new HttpPost(uri);
httpPost.setEntity(new StringEntity(payload));
processHeaderList(httpPost, jsonHeaders);
response = sendReceiveRequest(httpPost, username, password);
output = IOUtils.toString(response.getEntity().getContent());
headers = response.getAllHeaders();
httpStatus = response.getStatusLine().getStatusCode();
contentType = response.getEntity().getContentType().getValue();
if (log.isTraceEnabled()) {
log.trace("Invoked POST " + uri.toString() +
" - Input payload: " + payload + " - Response message: " + output);
}
EntityUtils.consume(response.getEntity());
} finally {
if (response != null) {
IOUtils.closeQuietly(response);
}
if (httpPost != null) {
httpPost.releaseConnection();
}
}
return new RESTResponse(contentType, output, headers, httpStatus);
}
/**
* Invokes the http PUT method
*
* @param uri endpoint/service url
* @param jsonHeaders header list
* @param username username for authentication
* @param password password for authentication
* @param payload payload body passed
* @return RESTResponse of the PUT request (can be the response body or the response status code)
* @throws Exception
*/
public RESTResponse invokePUT(URI uri, JsonNodeObject jsonHeaders, String username, String password,
String payload) throws IOException {
HttpPut httpPut = null;
CloseableHttpResponse response = null;
Header[] headers;
int httpStatus;
String contentType;
String output;
try {
httpPut = new HttpPut(uri);
httpPut.setEntity(new StringEntity(payload));
processHeaderList(httpPut, jsonHeaders);
response = sendReceiveRequest(httpPut, username, password);
output = IOUtils.toString(response.getEntity().getContent());
headers = response.getAllHeaders();
httpStatus = response.getStatusLine().getStatusCode();
contentType = response.getEntity().getContentType().getValue();
if (log.isTraceEnabled()) {
log.trace("Invoked PUT " + uri.toString() + " - Response message: " + output);
}
EntityUtils.consume(response.getEntity());
} finally {
if (response != null) {
IOUtils.closeQuietly(response);
}
if (httpPut != null) {
httpPut.releaseConnection();
}
}
return new RESTResponse(contentType, output, headers, httpStatus);
}
/**
* Invokes the http DELETE method
*
* @param uri endpoint/service url
* @param jsonHeaders header list
* @param username username for authentication
* @param password password for authentication
* @return RESTResponse of the DELETE (can be the response status code or the response body)
* @throws Exception
*/
public RESTResponse invokeDELETE(URI uri, JsonNodeObject jsonHeaders, String username, String password) throws IOException {
HttpDelete httpDelete = null;
CloseableHttpResponse response = null;
Header[] headers;
int httpStatus;
String contentType;
String output;
try {
httpDelete = new HttpDelete(uri);
processHeaderList(httpDelete, jsonHeaders);
response = sendReceiveRequest(httpDelete, username, password);
output = IOUtils.toString(response.getEntity().getContent());
headers = response.getAllHeaders();
httpStatus = response.getStatusLine().getStatusCode();
contentType = response.getEntity().getContentType().getValue();
if (log.isTraceEnabled()) {
log.trace("Invoked DELETE " + uri.toString() + " - Response message: " + output);
}
EntityUtils.consume(response.getEntity());
} finally {
if (response != null) {
IOUtils.closeQuietly(response);
}
if (httpDelete != null) {
httpDelete.releaseConnection();
}
}
return new RESTResponse(contentType, output, headers, httpStatus);
}
}