blob: 6103339909820e255d929995972a2180d647f7a4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013-2016 LAAS-CNRS (www.laas.fr)
* 7 Colonel Roche 31077 Toulouse - France
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Initial Contributors:
* Thierry Monteil : Project manager, technical co-manager
* Mahdi Ben Alaya : Technical co-manager
* Samir Medjiah : Technical co-manager
* Khalil Drira : Strategy expert
* Guillaume Garzone : Developer
* François Aïssaoui : Developer
*
* New contributors :
*******************************************************************************/
package org.eclipse.om2m.binding.http;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
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.HttpUriRequest;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.eclipse.om2m.binding.http.constants.HttpHeaders;
import org.eclipse.om2m.binding.http.constants.HttpParameters;
import org.eclipse.om2m.binding.service.RestClientService;
import org.eclipse.om2m.commons.constants.MimeMediaType;
import org.eclipse.om2m.commons.constants.Operation;
import org.eclipse.om2m.commons.constants.ResponseStatusCode;
import org.eclipse.om2m.commons.resource.Attribute;
import org.eclipse.om2m.commons.resource.FilterCriteria;
import org.eclipse.om2m.commons.resource.RequestPrimitive;
import org.eclipse.om2m.commons.resource.ResponsePrimitive;
import org.eclipse.om2m.commons.utils.Util;
/**
* Provides mapping from a protocol-independent request to a HTTP-specific request.
*/
@SuppressWarnings("restriction")
public class RestHttpClient implements RestClientService {
/** Logger */
private static Log LOGGER = LogFactory.getLog(RestHttpClient.class);
/** implemented specific protocol name */
private static String protocol ="http";
/**
* gets the implemented specific protocol name
* @return protocol name
*/
public String getProtocol() {
return protocol;
}
/**
* Converts a protocol-independent {@link RequestPrimitive} object into a standard HTTP request and sends a standard HTTP request.
* Converts the received standard HTTP request into {@link ResponsePrimitive} object and returns it back.
* @param requestPrimitive - protocol independent request.
* @return protocol independent response.
*/
public ResponsePrimitive sendRequest(RequestPrimitive requestPrimitive) {
LOGGER.info("Sending request: " + requestPrimitive);
CloseableHttpClient httpClient = HttpClientBuilder.create()
.disableAutomaticRetries()
.useSystemProperties()
.build();
ResponsePrimitive responsePrimitive = new ResponsePrimitive(requestPrimitive);
HttpUriRequest method = null;
// Retrieve the url
String url = requestPrimitive.getTo();
if(!url.startsWith(protocol/*+"://"*/)){
if (url.startsWith("://")){
url = protocol + url;
} else if (url.startsWith("//")){
url = protocol + ":" + url;
} else {
url = protocol + "://" + url ;
}
}
Map<String, List<String>> parameters = getParameters(requestPrimitive);
parameters.putAll(requestPrimitive.getQueryStrings());
if(!parameters.isEmpty()){
String queryString = "";
for(String parameter : parameters.keySet()){
for(String value : parameters.get(parameter)){
queryString += "&" + parameter + "=" + value;
}
}
queryString = queryString.replaceFirst("&", "?");
LOGGER.info("Query string generated: " + queryString);
url += queryString;
}
try {
// Set the operation
BigInteger operation = requestPrimitive.getOperation();
if (operation != null){
if (operation.equals(Operation.CREATE)){
method = new HttpPost(url);
if(requestPrimitive.getContent() != null){
((HttpPost) method).setEntity(
new StringEntity((String)requestPrimitive.getContent()));
}
} else if (operation.equals(Operation.RETRIEVE)){
method = new HttpGet(url);
} else if (operation.equals(Operation.UPDATE)){
method = new HttpPut(url);
if(requestPrimitive.getContent() != null){
((HttpPut) method).setEntity(
new StringEntity((String)requestPrimitive.getContent()));
}
} else if (operation.equals(Operation.DELETE)){
method = new HttpDelete(url);
} else if (operation.equals(Operation.NOTIFY)){
method = new HttpPost(url);
if(requestPrimitive.getContent() != null){
((HttpPost) method).setEntity(
new StringEntity((String)requestPrimitive.getContent()));
}
}
} else {
return null;
}
// Set the return content type
method.addHeader(HttpHeaders.ACCEPT, requestPrimitive.getReturnContentType());
// Set the request content type
String contentTypeHeader = requestPrimitive.getRequestContentType();
// Set the request identifier header
if (requestPrimitive.getRequestIdentifier() != null){
method.addHeader(HttpHeaders.REQUEST_IDENTIFIER,
requestPrimitive.getRequestIdentifier());
}
// Set the originator header
if (requestPrimitive.getFrom() != null){
method.addHeader(HttpHeaders.ORIGINATOR,
requestPrimitive.getFrom());
}
// add any custom headers (request.httpHeaders)
if (!requestPrimitive.getHttpHeaders().isEmpty()) {
for(String httpHeaderKey : requestPrimitive.getHttpHeaders().keySet()) {
method.addHeader(httpHeaderKey, requestPrimitive.getHttpHeaders().get(httpHeaderKey));
}
}
// Add the content type header with the resource type for create operation
if (requestPrimitive.getResourceType() != null){
contentTypeHeader += ";ty=" + requestPrimitive.getResourceType().toString();
}
method.addHeader(HttpHeaders.CONTENT_TYPE, contentTypeHeader);
// Add the notification URI in the case of non-blocking request
if(requestPrimitive.getResponseTypeInfo() != null){
String uris = "";
for(String notifUri : requestPrimitive.getResponseTypeInfo().getNotificationURI()){
uris += "&" + notifUri;
}
uris = uris.replaceFirst("&", "");
method.addHeader(HttpHeaders.RESPONSE_TYPE, uris);
}
LOGGER.info("Request to be send: " + method.toString());
String headers = "";
for (Header h : method.getAllHeaders()){
headers += h.toString() + "\n" ;
}
LOGGER.info("Headers:\n" + headers);
HttpResponse httpResponse = httpClient.execute(method);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if(httpResponse.getFirstHeader(HttpHeaders.RESPONSE_STATUS_CODE) != null){
responsePrimitive.setResponseStatusCode(new BigInteger(httpResponse.getFirstHeader(HttpHeaders.RESPONSE_STATUS_CODE).getValue()));
} else {
responsePrimitive.setResponseStatusCode(getResponseStatusCode(httpResponse, statusCode));
}
if (statusCode != 204){
if (httpResponse.getEntity() != null){
responsePrimitive.setContent(Util.convertStreamToString
(httpResponse.getEntity().getContent()));
if(httpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE) != null){
responsePrimitive.setContentType(httpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
}
}
}
if (statusCode == 201){
String contentHeader = "";
for (Header header : httpResponse.getHeaders(HttpHeaders.CONTENT_LOCATION)){
contentHeader += header.getValue();
}
responsePrimitive.setLocation(contentHeader);
}
LOGGER.info("Http Client response: " + responsePrimitive);
} catch(HttpHostConnectException e){
LOGGER.info("Target is not reachable: " + requestPrimitive.getTo());
responsePrimitive.setResponseStatusCode(ResponseStatusCode.TARGET_NOT_REACHABLE);
responsePrimitive.setContent("Target is not reachable: " + requestPrimitive.getTo());
responsePrimitive.setContentType(MimeMediaType.TEXT_PLAIN);
} catch (IOException e){
LOGGER.error(url + " not found", e);
responsePrimitive.setResponseStatusCode(ResponseStatusCode.TARGET_NOT_REACHABLE);
} finally {
try {
httpClient.close();
} catch (IOException e) {
// Silently fail
LOGGER.trace("Fail closing the http client", e);
}
}
return responsePrimitive;
}
private Map<String, List<String>> getParameters(RequestPrimitive request) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
List<String> list;
if(request.getResultContent() != null){
list = new ArrayList<String>();
list.add(request.getResultContent().toString());
map.put(HttpParameters.RESULT_CONTENT, list);
}
if(request.getDiscoveryResultType() != null){
list = new ArrayList<String>();
list.add(request.getDiscoveryResultType().toString());
map.put(HttpParameters.DISCOVERY_RESULT_TYPE, list);
}
if(request.getFilterCriteria() != null){
FilterCriteria filter = request.getFilterCriteria();
if(!filter.getAttribute().isEmpty()){
for(Attribute att : filter.getAttribute()){
list = new ArrayList<String>();
list.add(att.getValue().toString());
map.put(att.getName(), list);
}
}
if(!filter.getContentType().isEmpty()){
list = new ArrayList<String>();
list.addAll(filter.getContentType());
map.put(HttpParameters.CONTENT_TYPE, list);
}
if(filter.getCreatedAfter() != null){
list = new ArrayList<String>();
list.add(filter.getCreatedAfter());
map.put(HttpParameters.CREATED_AFTER, list);
}
if(filter.getCreatedBefore() != null){
list = new ArrayList<String>();
list.add(filter.getCreatedBefore());
map.put(HttpParameters.CREATED_BEFORE, list);
}
if(filter.getExpireAfter() != null){
list = new ArrayList<String>();
list.add(filter.getExpireAfter());
map.put(HttpParameters.EXPIRE_AFTER,list);
}
if(filter.getExpireBefore() != null){
list = new ArrayList<String>();
list.add(filter.getExpireBefore());
map.put(HttpParameters.EXPIRE_BEFORE, list);
}
if(filter.getFilterUsage() != null){
list = new ArrayList<String>();
list.add(filter.getFilterUsage().toString());
map.put(HttpParameters.FILTER_USAGE, list);
}
if(!filter.getLabels().isEmpty()){
list = new ArrayList<String>();
list.addAll(filter.getLabels());
map.put(HttpParameters.LABELS, list);
}
if(filter.getLimit() != null){
list = new ArrayList<String>();
list.add(filter.getLimit().toString());
map.put(HttpParameters.LIMIT, list);
}
if(filter.getModifiedSince() != null){
list = new ArrayList<String>();
list.add(filter.getModifiedSince());
map.put(HttpParameters.MODIFIED_SINCE, list);
}
if(filter.getResourceType() != null){
list = new ArrayList<String>();
list.add(filter.getResourceType().toString());
map.put(HttpParameters.RESOURCE_TYPE, list);
}
if(filter.getSizeAbove() != null){
list = new ArrayList<String>();
list.add(filter.getSizeAbove().toString());
map.put(HttpParameters.SIZE_ABOVE, list);
}
if(filter.getSizeBelow() != null){
list = new ArrayList<String>();
list.add(filter.getSizeBelow().toString());
map.put(HttpParameters.SIZE_BELOW, list);
}
if(filter.getStateTagBigger() != null){
list = new ArrayList<String>();
list.add(filter.getStateTagBigger().toString());
map.put(HttpParameters.STATE_TAG_BIGGER, list);
}
if(filter.getStateTagSmaller() != null){
list = new ArrayList<String>();
list.add(filter.getStateTagSmaller().toString());
map.put(HttpParameters.STATE_TAG_SMALLER, list);
}
if(filter.getUnmodifiedSince() != null){
list = new ArrayList<String>();
list.add(filter.getUnmodifiedSince());
map.put(HttpParameters.UNMODIFIED_SINCE, list);
}
}
return map;
}
/**
* Converts a standard HTTP status code into a protocol-independent {@link ResponseStatusCode} object.
* @param statusCode - standard HTTP status code.
* @return protocol independent status.
*/
private BigInteger getResponseStatusCode(HttpResponse response, int statusCode) {
if(response.getHeaders(HttpHeaders.RESPONSE_STATUS_CODE) != null
&& response.getHeaders(HttpHeaders.RESPONSE_STATUS_CODE).length > 0){
return new BigInteger(response.getHeaders(HttpHeaders.RESPONSE_STATUS_CODE)[0].getValue());
} else {
switch(statusCode){
case 200: return ResponseStatusCode.OK;
case 202: return ResponseStatusCode.ACCEPTED;
case 201: return ResponseStatusCode.CREATED;
case 204: return ResponseStatusCode.DELETED;
case 400: return ResponseStatusCode.BAD_REQUEST;
case 403: return ResponseStatusCode.ACCESS_DENIED;
case 404: return ResponseStatusCode.NOT_FOUND;
case 405: return ResponseStatusCode.OPERATION_NOT_ALLOWED;
case 409: return ResponseStatusCode.CONFLICT;
case 500: return ResponseStatusCode.INTERNAL_SERVER_ERROR;
case 501: return ResponseStatusCode.NOT_IMPLEMENTED;
case 503: return ResponseStatusCode.SERVICE_UNAVAILABLE;
default: return ResponseStatusCode.INTERNAL_SERVER_ERROR;
}
}
}
/**
* Converts a protocol independent parameters into a standard HTTP parameters.
* @param params - protocol independent parameters map.
* @return standard HTTP query string.
*/
private static String getQueryFromParams(Map<String, List<String>> params){
String query;
List<String> values = new ArrayList<String>();
String name;
if (params != null) {
query="?";
Iterator<String> it = params.keySet().iterator();
while(it.hasNext()){
name = it.next().toString();
values = params.get(name);
for(int i=0;i<values.size();i++){
query = query+name+"="+values.get(i)+"&";
}
}
return query;
}
return null;
}
}