/** * --| ADAPTIVE RUNTIME PLATFORM |---------------------------------------------------------------------------------------- * <p/> * (C) Copyright 2013-2015 Carlos Lozano Diez t/a Adaptive.me <http://adaptive.me>. * <p/> * 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 appli- * -cable 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. * <p/> * Original author: * <p/> * Carlos Lozano Diez * <http://github.com/carloslozano> * <http://twitter.com/adaptivecoder> * <mailto:carlos@adaptive.me> * <p/> * Contributors: * <p/> * Ferran Vila Conesa * <http://github.com/fnva> * <http://twitter.com/ferran_vila> * <mailto:ferran.vila.conesa@gmail.com> * <p/> * See source code files for contributors. * <p/> * Release: * * @version v2.0.3 * <p/> * -------------------------------------------| aut inveniam viam aut faciam |-------------------------------------------- */ package me.adaptive.arp.impl; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import me.adaptive.arp.api.AppRegistryBridge; import me.adaptive.arp.api.BaseCommunicationDelegate; import me.adaptive.arp.api.ILogging; import me.adaptive.arp.api.ILoggingLogLevel; import me.adaptive.arp.api.IService; import me.adaptive.arp.api.IServiceContentEncoding; import me.adaptive.arp.api.IServiceMethod; import me.adaptive.arp.api.IServiceResultCallback; import me.adaptive.arp.api.IServiceResultCallbackError; import me.adaptive.arp.api.IServiceResultCallbackWarning; import me.adaptive.arp.api.Service; import me.adaptive.arp.api.ServiceEndpoint; import me.adaptive.arp.api.ServiceHeader; import me.adaptive.arp.api.ServicePath; import me.adaptive.arp.api.ServiceRequest; import me.adaptive.arp.api.ServiceRequestParameter; import me.adaptive.arp.api.ServiceResponse; import me.adaptive.arp.api.ServiceSession; import me.adaptive.arp.api.ServiceSessionAttribute; import me.adaptive.arp.api.ServiceSessionCookie; import me.adaptive.arp.api.ServiceToken; import me.adaptive.arp.common.Utils; import me.adaptive.arp.common.parser.xml.XmlParser; /** * Interface for Managing the Services operations * Auto-generated implementation of IService specification. */ public class ServiceDelegate extends BaseCommunicationDelegate implements IService { // logger private static final String LOG_TAG = "ServiceDelegate"; private ILogging logger; // List of service sessions private Map<String, Session> serviceSession; /** * Default Constructor. */ public ServiceDelegate() { super(); logger = AppRegistryBridge.getInstance().getLoggingBridge(); serviceSession = new HashMap<>(); } /** * Obtains a Service token by a concrete uri (http://domain.com/path). This method would be useful when * a services response is a redirect (3XX) and it is necessary to make a request to another host and we * don't know by advance the name of the services. * * @param uri Unique Resource Identifier for a Service-Endpoint-Path-Method * @return ServiceToken to create a services request or null if the given parameter is not * configured in the platform's XML services definition file. * @since v2.1.4 */ @Override public ServiceToken getServiceTokenByUri(String uri) { URL url; if (!Utils.validateURI(uri, "^https?://.*")) return null; try { url = new URL(uri); } catch (MalformedURLException e) { logger.log(ILoggingLogLevel.Error, LOG_TAG, "uri Error: " + e.getLocalizedMessage()); return null; } for (Service ser : XmlParser.getInstance().getServices().values()) for (ServiceEndpoint endpoint : ser.getServiceEndpoints()) if (url.getProtocol().concat("://").concat(url.getHost()).equals(endpoint.getHostURI())) for (ServicePath path : endpoint.getPaths()) if (url.getPath().equals(path.getPath())) return new ServiceToken(ser.getName(), endpoint.getHostURI(), path.getPath(), path.getMethods()[0]); return null; } /** * Create a services request for the given ServiceToken. This method creates the request, populating * existing headers and cookies for the same services. The request is populated with all the defaults * for the services being invoked and requires only the request body to be set. Headers and cookies may be * manipulated as needed by the application before submitting the ServiceRequest via invokeService. * * @param serviceToken ServiceToken to be used for the creation of the request. * @return ServiceRequest with pre-populated headers, cookies and defaults for the services. * @since v2.0.6 */ @Override public ServiceRequest getServiceRequest(ServiceToken serviceToken) { if (!isServiceRegistered(serviceToken)) { return null; } ServiceRequest request = new ServiceRequest(null, serviceToken); request.setContentEncoding(IServiceContentEncoding.Utf8); request.setServiceToken(serviceToken); AppContextWebviewDelegate webViewDelegate = (AppContextWebviewDelegate) AppRegistryBridge.getInstance().getPlatformContextWeb().getDelegate(); request.setUserAgent(webViewDelegate.getUserAgent()); request.setContentType(String.valueOf(XmlParser.getInstance().getContentType(serviceToken))); if (serviceSession.containsKey(serviceToken.getEndpointName())) { Session session = serviceSession.get(serviceToken.getEndpointName()); request.setServiceHeaders(session.headers); request.setServiceSession(new ServiceSession(session.cookies, session.attributes)); request.setUserAgent(session.userAgent); } return request; } /** * Obtains a ServiceToken for the given parameters to be used for the creation of requests. * * @param serviceName Service name. * @param endpointName Endpoint name. * @param functionName Function name. * @param method Method type. * @return ServiceToken to create a services request or null if the given parameter combination is not * configured in the platform's XML services definition file. * @since v2.0.6 */ @Override public ServiceToken getServiceToken(String serviceName, String endpointName, String functionName, IServiceMethod method) { if (XmlParser.getInstance().getServices().containsKey(serviceName)) { Service serv = XmlParser.getInstance().getServices().get(serviceName); for (ServiceEndpoint endpoint : serv.getServiceEndpoints()) { if (endpoint.getHostURI().equals(endpointName)) { for (ServicePath path : endpoint.getPaths()) { if (path.getPath().equals(functionName)) { for (IServiceMethod serviceMethod : path.getMethods()) { if (serviceMethod.equals(method)) { return new ServiceToken(serviceName, endpoint.getHostURI(), path.getPath(), serviceMethod); } } } } } } } return null; } /** * Returns all the possible services tokens configured in the platform's XML services definition file. * * @return Array of services tokens configured. * @since v2.0.6 */ @Override public ServiceToken[] getServicesRegistered() { List<ServiceToken> tokens = new ArrayList<>(); for (Service serv : XmlParser.getInstance().getServices().values()) { for (ServiceEndpoint endpoint : serv.getServiceEndpoints()) { for (ServicePath path : endpoint.getPaths()) { for (IServiceMethod method : path.getMethods()) { tokens.add(new ServiceToken(serv.getName(), endpoint.getHostURI(), path.getPath(), method)); } } } } return tokens.toArray(new ServiceToken[tokens.size()]); } /** * Executes the given ServiceRequest and provides responses to the given callback handler. * * @param serviceRequest ServiceRequest with the request body. * @param callback IServiceResultCallback to handle the ServiceResponse. * @since v2.0.6 */ @Override public void invokeService(ServiceRequest serviceRequest, IServiceResultCallback callback) { if (!Utils.validateService(serviceRequest.getServiceToken())) { callback.onError(IServiceResultCallbackError.NotRegisteredService); return; } //HttpClient httpClient = new DefaultHttpClient(); ServiceResponse serviceResponse = new ServiceResponse(); //HttpResponse response = null; //String url = null; try { URL url = new URL(getURL(serviceRequest)); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); /*switch (serviceRequest.getServiceToken().getInvocationMethod()) { case Get: url = getURL(serviceRequest); response = httpClient.execute(new HttpGet(url)); break; case Post: break; case Head: break; default: }*/ //StatusLine statusLine = response.getStatusLine(); //int status = statusLine.getStatusCode(); int status = connection.getResponseCode(); if (isBetween(status, 200, 406) || isBetween(status, 500, 599)) { /*ByteArrayOutputStream out = new ByteArrayOutputStream(); response.getEntity().writeTo(out); String responseString = out.toString(); out.close();*/ String responseString; try { InputStream in = new BufferedInputStream(connection.getInputStream()); responseString = Utils.getStringFromInputStream(in); } finally { connection.disconnect(); } // TODO: Prepare the session attributes //serviceResponse.setContentType(EntityUtils.getContentCharSet(response.getEntity())); serviceResponse.setContentType(connection.getContentType()); serviceResponse.setContentEncoding(IServiceContentEncoding.Utf8); serviceResponse.setContent(responseString); serviceResponse.setContentLength(responseString.length()); //serviceResponse.setServiceHeaders(getHeaders(response.getAllHeaders())); serviceResponse.setServiceHeaders(getHeaders(connection)); serviceResponse.setStatusCode(status); if (!url.getProtocol().startsWith("https://")) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Not Secured URL (https): " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.NotSecure); return; } if (isBetween(status, 200, 299)) { callback.onResult(serviceResponse); return; } else if (isBetween(status, 300, 399)) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Redirected Response"); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.Redirected); return; } else if (status == 400) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Wrong params: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.WrongParams); return; } else if (status == 401) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Not authenticaded: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.NotAuthenticated); return; } else if (status == 402) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Payment Required: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.PaymentRequired); return; } else if (status == 403) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Forbidden: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.Forbidden); return; } else if (status == 404) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "NotFound: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.NotFound); return; } else if (status == 405) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Method not allowed: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.MethodNotAllowed); return; } else if (status == 406) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Not allowed: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.NotAllowed); return; } if (isBetween(status, 500, 599)) { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "Server error: " + url); callback.onWarning(serviceResponse, IServiceResultCallbackWarning.ServerError); return; } else { logger.log(ILoggingLogLevel.Warn, LOG_TAG, "The status code received [" + status + "] is not handled by the platform"); callback.onError(IServiceResultCallbackError.Unreachable); return; } } else if (status == 408) { logger.log(ILoggingLogLevel.Error, LOG_TAG, "There is a timeout calling the service: " + url); callback.onError(IServiceResultCallbackError.TimeOut); return; } else if (status == 444) { logger.log(ILoggingLogLevel.Error, LOG_TAG, "There is no response calling the service: " + url); callback.onError(IServiceResultCallbackError.NoResponse); return; } else { logger.log(ILoggingLogLevel.Error, LOG_TAG, "The status code received [" + status + "] is not handled by the platform"); callback.onError(IServiceResultCallbackError.Unknown); return; } } catch (IOException e) { logger.log(ILoggingLogLevel.Error, LOG_TAG, "invokeService Error: " + e.getLocalizedMessage()); /*assert response != null; try { response.getEntity().getContent().close(); } catch (IOException e1) { logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error closing the response: " + e1.getLocalizedMessage()); }*/ } logger.log(ILoggingLogLevel.Error, LOG_TAG, "The status code received is not handled by the platform"); callback.onError(IServiceResultCallbackError.Unreachable); } /** * Get ServiceHeader[] from Header[] * * @param connection URLConnection * @return serviceHeader array */ private ServiceHeader[] getHeaders(URLConnection connection) { ServiceHeader[] serviceHeaders = new ServiceHeader[0]; Map<String, List<String>> map = connection.getHeaderFields(); for (Map.Entry<String, List<String>> entry : map.entrySet()) { ServiceHeader serviceHeader = new ServiceHeader(entry.getKey(), entry.getValue().get(0)); serviceHeaders = Utils.addElement(serviceHeaders, serviceHeader); } return serviceHeaders; } /** * Return the url string from a ServiceRequest * * @param request Request object * @return Url string */ public String getURL(ServiceRequest request) { String urlString; ServiceToken token = request.getServiceToken(); String parameters = ""; if (request.getQueryParameters() != null) { for (ServiceRequestParameter serviceRequestParameter : request.getQueryParameters()) { if (!parameters.isEmpty()) parameters += "&"; parameters += serviceRequestParameter.getKeyName() + "=" + serviceRequestParameter.getKeyData(); } } urlString = token.getEndpointName() + token.getFunctionName() + (parameters.isEmpty() ? "" : ("?" + parameters)); return urlString; } /** * Checks whether a specific services, endpoint, function and method type is configured in the platform's * XML services definition file. * * @param serviceName Service name. * @param endpointName Endpoint name. * @param functionName Function name. * @param method Method type. * @return Returns true if the services is configured, false otherwise. * @since v2.0.6 */ @Override public boolean isServiceRegistered(String serviceName, String endpointName, String functionName, IServiceMethod method) { if (XmlParser.getInstance().getServices().containsKey(serviceName)) { Service serv = XmlParser.getInstance().getServices().get(serviceName); for (ServiceEndpoint endpoint : serv.getServiceEndpoints()) { if (endpoint.getHostURI().equals(endpointName)) { for (ServicePath path : endpoint.getPaths()) { if (path.getPath().equals(functionName)) { for (IServiceMethod serviceMethod : path.getMethods()) { if (serviceMethod.equals(method)) return true; } } } } } } return false; } /** * Method for testing if a service is registered * * @param serviceToken Service Token to test * @return TRue is registered, false otherwise */ private boolean isServiceRegistered(ServiceToken serviceToken) { return isServiceRegistered(serviceToken.getServiceName(), serviceToken.getEndpointName(), serviceToken.getFunctionName(), serviceToken.getInvocationMethod()); } /** * Check if a number is between two numbers * * @param x Number to check * @param lower Lower number * @param upper Upper number * @return Returns true or false */ private boolean isBetween(int x, int lower, int upper) { return lower <= x && x <= upper; } /** * Internal class to represent a session object travelling for the ARP */ private class Session { ServiceHeader[] headers; ServiceSessionCookie[] cookies; ServiceSessionAttribute[] attributes; String userAgent; } } /** * ------------------------------------| Engineered with ♥ in Barcelona, Catalonia |-------------------------------------- */