Java tutorial
/* * Copyright (c) 2015 Cisco Systems, Inc. and others. 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 */ package org.opendaylight.iotdm.onem2m.protocols.http; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.eclipse.jetty.client.Address; import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext; import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; import org.opendaylight.iotdm.onem2m.client.Onem2mRequestPrimitiveClientBuilder; import org.opendaylight.iotdm.onem2m.core.Onem2m; import org.opendaylight.iotdm.onem2m.client.Onem2mRequestPrimitiveClient; import org.opendaylight.iotdm.onem2m.core.Onem2mStats; import org.opendaylight.iotdm.onem2m.core.rest.utils.RequestPrimitive; import org.opendaylight.iotdm.onem2m.core.rest.utils.ResponsePrimitive; import org.opendaylight.iotdm.onem2m.core.router.Onem2mRouterPlugin; import org.opendaylight.iotdm.onem2m.core.router.Onem2mRouterService; import org.opendaylight.iotdm.onem2m.notifier.Onem2mNotifierPlugin; import org.opendaylight.iotdm.onem2m.notifier.Onem2mNotifierService; import org.opendaylight.iotdm.onem2m.plugins.*; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.iotdm.onem2m.rev150105.Onem2mService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Onem2mHttpProvider implements AbstractIotDMPlugin, Onem2mNotifierPlugin, Onem2mRouterPlugin, BindingAwareProvider, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(Onem2mHttpProvider.class); protected Onem2mService onem2mService; private HttpClient client; private final String PLUGIN_NAME = "http"; @Override public void onSessionInitiated(ProviderContext session) { onem2mService = session.getRpcService(Onem2mService.class); Onem2mNotifierService.getInstance().pluginRegistration(this); Onem2mRouterService.getInstance().pluginRegistration(this); LOG.info("Onem2mHttpProvider Session Initiated"); } @Override public void close() throws Exception { LOG.info("Onem2mHttpProvider Closed"); } public Onem2mHttpProvider() { Onem2mPluginManager mgr = Onem2mPluginManager.getInstance(); mgr.registerPluginAtPort("http", this, 8282, Onem2mPluginManager.Mode.Exclusive); } private String resolveContentFormat(String contentType) { if (contentType.contains("json")) { return Onem2m.ContentFormat.JSON; } else if (contentType.contains("xml")) { return Onem2m.ContentFormat.XML; } return null; } public void handle(IotDMPluginRequest request, IotDMPluginResponse response) { HttpServletRequest httpRequest = ((IotDMPluginHttpRequest) request).getHttpRequest(); HttpServletResponse httpResponse = ((IotDMPluginHttpResponse) response).getHttpResponse(); Onem2mRequestPrimitiveClientBuilder clientBuilder = new Onem2mRequestPrimitiveClientBuilder(); String headerValue; clientBuilder.setProtocol(Onem2m.Protocol.HTTP); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS); String contentType = httpRequest.getContentType(); if (contentType == null) contentType = "json"; contentType = contentType.toLowerCase(); if (contentType.contains("json")) { clientBuilder.setContentFormat(Onem2m.ContentFormat.JSON); } else if (contentType.contains("xml")) { clientBuilder.setContentFormat(Onem2m.ContentFormat.XML); } else { httpResponse.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); try { httpResponse.getWriter().println("Unsupported media type: " + contentType); } catch (IOException e) { e.printStackTrace(); } httpResponse.setContentType("text/json;charset=utf-8"); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_ERROR); return; } clientBuilder.setTo(Onem2m.translateUriToOnem2m(httpRequest.getRequestURI())); // pull fields out of the headers headerValue = httpRequest.getHeader(Onem2m.HttpHeaders.X_M2M_ORIGIN); if (headerValue != null) { clientBuilder.setFrom(headerValue); } headerValue = httpRequest.getHeader(Onem2m.HttpHeaders.X_M2M_RI); if (headerValue != null) { clientBuilder.setRequestIdentifier(headerValue); } headerValue = httpRequest.getHeader(Onem2m.HttpHeaders.X_M2M_NM); if (headerValue != null) { clientBuilder.setName(headerValue); } headerValue = httpRequest.getHeader(Onem2m.HttpHeaders.X_M2M_GID); if (headerValue != null) { clientBuilder.setGroupRequestIdentifier(headerValue); } headerValue = httpRequest.getHeader(Onem2m.HttpHeaders.X_M2M_RTU); if (headerValue != null) { clientBuilder.setResponseType(headerValue); } headerValue = httpRequest.getHeader(Onem2m.HttpHeaders.X_M2M_OT); if (headerValue != null) { clientBuilder.setOriginatingTimestamp(headerValue); } // the contentType string can have ty=val attached to it so we should handle this case Boolean resourceTypePresent = false; String contentTypeResourceString = parseContentTypeForResourceType(contentType); if (contentTypeResourceString != null) { resourceTypePresent = clientBuilder.parseQueryStringIntoPrimitives(contentTypeResourceString); } String method = httpRequest.getMethod().toLowerCase(); // look in query string if didnt find it in contentType header if (!resourceTypePresent) { resourceTypePresent = clientBuilder.parseQueryStringIntoPrimitives(httpRequest.getQueryString()); } else { clientBuilder.parseQueryStringIntoPrimitives(httpRequest.getQueryString()); } if (resourceTypePresent && !method.contentEquals("post")) { httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); try { httpResponse.getWriter().println("Specifying resource type not permitted."); } catch (IOException e) { e.printStackTrace(); } Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_ERROR); return; } // take the entire payload text and put it in the CONTENT field; it is the representation of the resource String cn = request.getPayLoad(); if (cn != null && !cn.contentEquals("")) { clientBuilder.setPrimitiveContent(cn); } switch (method) { case "get": clientBuilder.setOperationRetrieve(); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_RETRIEVE); break; case "post": if (resourceTypePresent) { clientBuilder.setOperationCreate(); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_CREATE); } else { clientBuilder.setOperationNotify(); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_NOTIFY); } break; case "put": clientBuilder.setOperationUpdate(); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_UPDATE); break; case "delete": clientBuilder.setOperationDelete(); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_DELETE); break; default: httpResponse.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); try { httpResponse.getWriter().println("Unsupported method type: " + method); } catch (IOException e) { e.printStackTrace(); } httpResponse.setContentType("text/json;charset=utf-8"); //baseRequest.setHandled(true); Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_ERROR); return; } // invoke the service request Onem2mRequestPrimitiveClient onem2mRequest = clientBuilder.build(); ResponsePrimitive onem2mResponse = Onem2m.serviceOnenm2mRequest(onem2mRequest, onem2mService); // now place the fields from the onem2m result response back in the http fields, and send try { sendHttpResponseFromOnem2mResponse(httpResponse, onem2mResponse); } catch (IOException e) { e.printStackTrace(); } } private void sendHttpResponseFromOnem2mResponse(HttpServletResponse httpResponse, ResponsePrimitive onem2mResponse) throws IOException { // the content is already in the required format ... String content = onem2mResponse.getPrimitive(ResponsePrimitive.CONTENT); String rscString = onem2mResponse.getPrimitive(ResponsePrimitive.RESPONSE_STATUS_CODE); String rqi = onem2mResponse.getPrimitive(ResponsePrimitive.REQUEST_IDENTIFIER); if (rqi != null) { httpResponse.setHeader(Onem2m.HttpHeaders.X_M2M_RI, rqi); } int httpRSC = mapCoreResponseToHttpResponse(httpResponse, rscString); if (content != null) { httpResponse.setStatus(httpRSC); httpResponse.getWriter().println(content); } else { httpResponse.setStatus(httpRSC); } if (rscString.charAt(0) == '2') { Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_OK); } else { Onem2mStats.getInstance().inc(Onem2mStats.HTTP_REQUESTS_ERROR); } String ct = onem2mResponse.getPrimitive(ResponsePrimitive.HTTP_CONTENT_TYPE); if (ct != null) { httpResponse.setContentType(ct); } String cl = onem2mResponse.getPrimitive(ResponsePrimitive.CONTENT_LOCATION); if (cl != null) { httpResponse.setHeader("Content-Location", Onem2m.translateUriFromOnem2m(cl)); } } private int mapCoreResponseToHttpResponse(HttpServletResponse httpResponse, String rscString) { httpResponse.setHeader(Onem2m.HttpHeaders.X_M2M_RSC, rscString); switch (rscString) { case Onem2m.ResponseStatusCode.OK: return HttpServletResponse.SC_OK; case Onem2m.ResponseStatusCode.CREATED: return HttpServletResponse.SC_CREATED; case Onem2m.ResponseStatusCode.CHANGED: return HttpServletResponse.SC_OK; case Onem2m.ResponseStatusCode.DELETED: return HttpServletResponse.SC_OK; case Onem2m.ResponseStatusCode.NOT_FOUND: return HttpServletResponse.SC_NOT_FOUND; case Onem2m.ResponseStatusCode.OPERATION_NOT_ALLOWED: return HttpServletResponse.SC_METHOD_NOT_ALLOWED; case Onem2m.ResponseStatusCode.CONTENTS_UNACCEPTABLE: return HttpServletResponse.SC_BAD_REQUEST; case Onem2m.ResponseStatusCode.CONFLICT: return HttpServletResponse.SC_CONFLICT; case Onem2m.ResponseStatusCode.INTERNAL_SERVER_ERROR: return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; case Onem2m.ResponseStatusCode.NOT_IMPLEMENTED: return HttpServletResponse.SC_NOT_IMPLEMENTED; case Onem2m.ResponseStatusCode.TARGET_NOT_REACHABLE: return HttpServletResponse.SC_NOT_FOUND; case Onem2m.ResponseStatusCode.ALREADY_EXISTS: return HttpServletResponse.SC_FORBIDDEN; case Onem2m.ResponseStatusCode.TARGET_NOT_SUBSCRIBABLE: return HttpServletResponse.SC_FORBIDDEN; case Onem2m.ResponseStatusCode.NON_BLOCKING_REQUEST_NOT_SUPPORTED: return HttpServletResponse.SC_NOT_IMPLEMENTED; case Onem2m.ResponseStatusCode.INVALID_ARGUMENTS: return HttpServletResponse.SC_BAD_REQUEST; case Onem2m.ResponseStatusCode.INSUFFICIENT_ARGUMENTS: return HttpServletResponse.SC_BAD_REQUEST; } return HttpServletResponse.SC_BAD_REQUEST; } @Override public void init() { } @Override public void cleanup() { } @Override public String pluginName() { return "*"; } // implement the Onem2mNotifierPlugin interface @Override public String getNotifierPluginName() { return this.PLUGIN_NAME; } /** * HTTP notifications will be set out to subscribers interested in resources from the tree where they have have hung * onem2m subscription resources * @param url where do i send this onem2m notify message * @param payload contents of the notification */ @Override public void sendNotification(String url, String payload) { ContentExchange ex = new ContentExchange(); ex.setURL(url); ex.setRequestContentSource(new ByteArrayInputStream(payload.getBytes())); ex.setRequestContentType(Onem2m.ContentType.APP_VND_NTFY_JSON); Integer cl = payload != null ? payload.length() : 0; ex.setRequestHeader("Content-Length", cl.toString()); ex.setMethod("post"); LOG.debug("HTTP: Send notification uri: {}, payload: {}:", url, payload); try { client.send(ex); } catch (IOException e) { LOG.error("Dropping notification: uri: {}, payload: {}", url, payload); } } private String parseContentTypeForResourceType(String contentType) { String split[] = contentType.trim().split(";"); if (split.length != 2) { return null; } return split[1]; } @Override public String getRouterPluginName() { return this.PLUGIN_NAME; } /** * Implements method of Onem2mRouterPlugin, sends request to the host specified by nextHopUrl. * @param request The request to be sent. * @param nextHopUrl The URL of the next hop, is used as value of Host header. * @return Response to he request is returned. */ @Override public ResponsePrimitive sendRequestBlocking(RequestPrimitive request, String nextHopUrl) { ContentExchange ex = createContentExchangeReq(request, nextHopUrl); try { client.send(ex); int state = HttpExchange.STATUS_START; try { state = ex.waitForDone(); } catch (InterruptedException e) { LOG.error("Request forwarding interrupted: {}", e); return createResponseFailed(request); } switch (state) { case HttpExchange.STATUS_COMPLETED: break; case HttpExchange.STATUS_EXCEPTED: case HttpExchange.STATUS_EXPIRED: LOG.trace("Failed to forward request, exchange state: {}", state); return createResponseUnreachable(request); default: LOG.error("Unexpected request send result: {}", state); return createResponseFailed(request); } } catch (IOException e) { LOG.error("Failed to send request to nextHop: {}", nextHopUrl); return createResponseFailed(request); } // Translate the HTTP response to Onem2m response primitive and return return createResponseFromHttp(ex, request); } /** * Creates query string for the HTTP request * @param request Request primitive including primitive parameters * @return The http query string */ private String createRequestQueryString(RequestPrimitive request) { StringBuilder ret = new StringBuilder(); String value = null; for (String param : Onem2m.queryStringParameters) { value = request.getPrimitive(param); if (null != value) { if (0 == ret.length()) { ret.append("?"); } else { ret.append("&"); } ret.append(param).append("=").append(value); } } return ret.toString(); } /** * Cretes Content-Type header value string. * @param request Request primitive including primitive parameters * @return value of the Content-Type header */ private String createRequestContentTypeString(RequestPrimitive request) { StringBuilder ret = new StringBuilder(); String value = null; switch (request.getPrimitive(RequestPrimitive.CONTENT_FORMAT)) { case Onem2m.ContentFormat.JSON: ret.append(Onem2m.ContentType.APP_VND_RES_JSON); break; case Onem2m.ContentFormat.XML: ret.append(Onem2m.ContentType.APP_VND_RES_XML); break; default: LOG.error("Unsupported content format: {}", request.getPrimitive(RequestPrimitive.CONTENT_FORMAT)); return ret.toString(); } for (String param : Onem2m.queryStringParameters) { value = request.getPrimitive(param); if (null != value) { ret.append(";").append(param).append("=").append(value); } } return ret.toString(); } /** * Prepares instance of ContentExchange to be used for sending the request. * @param request The Onem2m request * @param hostURL The URL of the destination host * @return Prepared ContentExchange instance */ private ContentExchange createContentExchangeReq(RequestPrimitive request, String hostURL) { ContentExchange ex = new ContentExchange(true); try { // set method and url ex.setMethod(translateOperationToMethod(request.getPrimitive(RequestPrimitive.OPERATION))); // set URL properly URL host = new URL(hostURL); String url = Onem2m.translateUriFromOnem2m(request.getPrimitive(RequestPrimitive.TO)); // add query string url += createRequestQueryString(request); ex.setAddress(new Address(host.getHost(), host.getPort())); ex.setURI(url); // set headers //TODO add all headers ex.setRequestHeader(Onem2m.HttpHeaders.X_M2M_ORIGIN, request.getPrimitive(RequestPrimitive.FROM)); ex.setRequestHeader("Host", hostURL); ex.setRequestHeader(Onem2m.HttpHeaders.X_M2M_RI, request.getPrimitive(RequestPrimitive.REQUEST_IDENTIFIER)); // ex.setRequestHeader(Onem2m.HttpHeaders.X_M2M_NM, request.getPrimitive(RequestPrimitive.NAME)); ex.setRequestHeader(Onem2m.HttpHeaders.X_M2M_GID, request.getPrimitive(RequestPrimitive.GROUP_REQUEST_IDENTIFIER)); ex.setRequestHeader(Onem2m.HttpHeaders.X_M2M_OT, request.getPrimitive(RequestPrimitive.ORIGINATING_TIMESTAMP)); // set content if (null != request.getPrimitive(RequestPrimitive.CONTENT)) { ex.setRequestContentSource( new ByteArrayInputStream(request.getPrimitive(RequestPrimitive.CONTENT).getBytes())); ex.setRequestContentType(createRequestContentTypeString(request)); } } catch (Exception e) { LOG.error("Failed to create HTTP request from Onem2mRequest: {}", e); return null; } return ex; } /** * Creates Onem2m response in case of internal error * @param request Onem2m request * @return Onem2m response with error status code */ private ResponsePrimitive createResponseFailed(RequestPrimitive request) { ResponsePrimitive response = new ResponsePrimitive(); response.setPrimitive(ResponsePrimitive.REQUEST_IDENTIFIER, request.getPrimitive(RequestPrimitive.REQUEST_IDENTIFIER)); response.setRSC(Onem2m.ResponseStatusCode.INTERNAL_SERVER_ERROR, "Forwarding of HTTP request failed"); return response; } /** * Creates Onem2m response in case of unreachable target * @param request Onem2m request * @return Onem2m response with error status code */ private ResponsePrimitive createResponseUnreachable(RequestPrimitive request) { ResponsePrimitive response = new ResponsePrimitive(); response.setPrimitive(ResponsePrimitive.REQUEST_IDENTIFIER, request.getPrimitive(RequestPrimitive.REQUEST_IDENTIFIER)); response.setRSC(Onem2m.ResponseStatusCode.TARGET_NOT_REACHABLE, "Not reachable"); return response; } /** * Translates HTTP response to Onem2m response * @param ex The instance of ContentExchange including received response * @param request The original Onem2m request. Is used to create response * with error status code in case of unsuccessful * translation of the received HTTP response * @return Onem2m response */ private ResponsePrimitive createResponseFromHttp(ContentExchange ex, RequestPrimitive request) { ResponsePrimitive response = new ResponsePrimitive(); HttpFields fields = ex.getResponseFields(); String value = null; // Set values from HTTP header if (null != fields) { value = fields.getStringField(Onem2m.HttpHeaders.X_M2M_RI); if (null != value) { response.setPrimitive(ResponsePrimitive.REQUEST_IDENTIFIER, value); } else { LOG.trace("Response without X_M2M_RI header"); return createResponseUnreachable(request); } value = fields.getStringField(Onem2m.HttpHeaders.X_M2M_RSC); if (null != value) { response.setPrimitive(ResponsePrimitive.RESPONSE_STATUS_CODE, value); } else { LOG.trace("Response without X_M2M_RSC header"); return createResponseUnreachable(request); } String contentType = fields.getStringField("Content-Type"); if (contentType != null) { value = contentType.toLowerCase(); value = resolveContentFormat(value); } if (null != value) { response.setPrimitive(ResponsePrimitive.CONTENT_FORMAT, value); } if (contentType != null) { response.setPrimitive(ResponsePrimitive.HTTP_CONTENT_TYPE, contentType); } value = fields.getStringField("Content-Location"); if (value != null) { response.setPrimitive(ResponsePrimitive.CONTENT_LOCATION, Onem2m.translateUriToOnem2m(value)); } // TODO add next headers if needed } // set response content if exists try { value = ex.getResponseContent(); } catch (UnsupportedEncodingException e) { LOG.error("Failed to get response content: {}", e); return createResponseUnreachable(request); } if (null != value) { response.setPrimitive(ResponsePrimitive.CONTENT, value); } return response; } private String translateOperationToMethod(String operation) { switch (operation) { case Onem2m.Operation.CREATE: case Onem2m.Operation.NOTIFY: return "post"; case Onem2m.Operation.RETRIEVE: return "get"; case Onem2m.Operation.DELETE: return "delete"; case Onem2m.Operation.UPDATE: return "put"; default: throw new IllegalArgumentException("Operation " + operation + " not supported by HTTP plugin"); } } }