diff --git a/yada-api/src/main/java/com/novartis/opensource/yada/adaptor/RESTAdaptor.java b/yada-api/src/main/java/com/novartis/opensource/yada/adaptor/RESTAdaptor.java index a3e2ed20..64717d45 100644 --- a/yada-api/src/main/java/com/novartis/opensource/yada/adaptor/RESTAdaptor.java +++ b/yada-api/src/main/java/com/novartis/opensource/yada/adaptor/RESTAdaptor.java @@ -15,48 +15,49 @@ package com.novartis.opensource.yada.adaptor; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; import java.net.HttpCookie; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Proxy; import java.net.URL; import java.net.URLConnection; -import java.net.URLEncoder; -import java.security.InvalidKeyException; -import java.security.KeyFactory; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.TreeMap; +import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.HttpsURLConnection; - -import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; +import com.google.api.client.auth.oauth.OAuthParameters; +import com.google.api.client.auth.oauth.OAuthRsaSigner; +import com.google.api.client.http.BasicAuthentication; +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.javanet.NetHttpTransport; + import com.novartis.opensource.yada.ConnectionFactory; import com.novartis.opensource.yada.YADAQuery; import com.novartis.opensource.yada.YADAQueryConfigurationException; import com.novartis.opensource.yada.YADAQueryResult; import com.novartis.opensource.yada.YADARequest; +import com.novartis.opensource.yada.YADASecurityException; - +import org.json.JSONException; import org.json.JSONObject; @@ -76,32 +77,50 @@ public class RESTAdaptor extends Adaptor { * Constant equal to: {@value}. The character set name. * @since 8.7.0 */ - private final static String CHARSET_UTF8 = "UTF-8"; + private final static String PROP_KEYSTORE = "javax.net.ssl.keyStore"; + + /** + * Constant equal to: {@value}. The keystore password passed to the jvm. + * @since 8.7.0 + */ + private final static String PROP_KEYSTORE_PASS = "javax.net.ssl.keyStorePassword"; + + /** + * Constant equal to: {@value}. The {@code oauth} request parameter property name. + * @since 8.7.0 + */ + private final static String OAUTH_VERSION = "oauth_version"; + + /** + * Constant equal to: {@value}. The {@code oauth} request parameter property name. + * @since 8.7.0 + */ + private final static String OAUTH_SIGNATURE_METHOD = "oauth_signature_method"; + + /** + * Constant equal to: {@value}. The {@code oauth} request parameter property name. + * @since 8.7.0 + */ + private final static String OAUTH_VERIFIER = "oauth_verifier"; - private final static String OAUTH_VERIFIER = "oauth_verifier"; - private final static String OAUTH_TIMESTAMP = "oauth_timestamp"; - private final static String OAUTH_NONCE = "oauth_nonce"; - private final static String OAUTH_SIGNATURE = "oauth_signature"; - private final static String OAUTH_REALM = "OAuth realm"; - private final static String SPACE = " "; - private final static String EQUAL = "="; - private final static String QUOTE = "\""; - private final static String COMMA = ","; - private final static String AMP = "&"; - private final static String CR = "\r"; - private final static String LF = "\n"; - private final static String CRLF = CR+LF; - private final static char URL_QUERY_MARKER = '?'; - private final static String BODY = "body"; + /** + * Constant equal to: {@value}. The {@code oauth} request parameter property name. + * @since 8.7.0 + */ + private final static String OAUTH_PRIVATE_KEY = "oauth_private_key"; /** - * Secure random number generator to sign requests. - * @see OAuthParameters.java + * Constant equal to: {@value}. The {@code oauth} request parameter property name. * @since 8.7.0 */ - private final static SecureRandom RANDOM = new SecureRandom(); - private final static String ALGO_RSA = "RSA"; - private final static String ALGO_RSASHA1 = "SHA1withRSA"; + private final static String OAUTH_CONSUMER_KEY = "oauth_consumer_key"; + + /** + * Constant equal to: {@value}. The {@code oauth} request parameter property name. + * @since 8.7.0 + */ + private final static String OAUTH_TOKEN = "oauth_token"; + /** * Constant equal to: {@value} */ @@ -109,20 +128,24 @@ public class RESTAdaptor extends Adaptor { /** * Constant equal to: {@value} - * {@link JSONParams} key for delivery of {@code HTTP POST, PUT, PATCH} body content + * {@link com.novartis.opensource.yada.JSONParams} key for delivery of {@code HTTP POST, PUT, PATCH} body content * @since 8.5.0 */ private final static String YADA_PAYLOAD = "YADA_PAYLOAD"; /** - * Constant equal to: {@value} + * Constant equal to: {@value}. * Workaround for requests using {@code HTTP PATCH} * @since 8.5.0 */ - private final static String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; - private final static String X_AUTHORIZATION = "X-Authorization"; - private final static String CONTENT_LENGTH = "Content-length"; - /** + private final static String H_X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; + + /** + * Constant equal to: {@value}. For retrieving the default keystore. + */ + private static final String KEYSTORE_TYPE_JKS = "jks"; + + /** * Variable to hold the proxy server string if necessary */ private String proxy = null; @@ -138,7 +161,6 @@ public class RESTAdaptor extends Adaptor { */ private String method = YADARequest.METHOD_GET; - /** * Default constructor */ @@ -216,234 +238,72 @@ private String setPositionalParameterValues(YADAQuery yq, int row) { return urlStr; } - private void setAuthentication(YADAQuery yq, int row, HttpURLConnection hConn) throws YADAQueryConfigurationException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, IOException - { + /** + * Determines if OAuth or Basic Authentication should be applied given the {@link YADARequest} + * configuration, and proceeds accordingly. + * @param yq The query object defined in the {@link YADARequest} + * @param url The REST endpoint + * @return The {@link HttpRequestInitializer} to pass to the {@link HttpRequestFactory} + * @throws YADAQueryConfigurationException if both OAuth and Basic are defined in the request + * @throws YADASecurityException if authentication fails + * @since 8.7.0 + */ + private HttpRequestInitializer setAuthentication(YADAQuery yq, GenericUrl url) throws YADAQueryConfigurationException, YADASecurityException + { if(yq.hasParam(YADARequest.PS_OAUTH) || yq.hasParam(YADARequest.PL_OAUTH)) { - if(hConn.getURL().getUserInfo() != null) + if(url.getUserInfo() != null) { String msg = "A query cannot contain both basic and oauth configurations. " + "When using oauth, make sure the REST data source is not also configured with " + "basic auth values."; throw new YADAQueryConfigurationException(msg); } - String body = yq.getDataRow(row).get(YADA_PAYLOAD)[0]; - setAuthenticationOAuth(body,hConn); + return setAuthenticationOAuth(url); } - else if(hConn.getURL().getUserInfo() != null) + else if(url.getUserInfo() != null) { - setAuthenticatonBasic(hConn.getURL(), hConn); + return setAuthenticatonBasic(url); } + return null; } - /** - * Generates a random nonce. This method originated at - * developer.pearson.com - * Oauth 1.0a Sample Code - * - * @return A unique identifier for the request - * @since 8.7.0 - */ - private static String getNonce() - { - return Long.toHexString(Math.abs(RANDOM.nextLong())); - } - /** - * Generates an integer representing the number of seconds since the unix epoch using the - * date/time the request is issued. This method originated at - * developer.pearson.com - * Oauth 1.0a Sample Code - * - * - * @return A timestamp for the request - * @since 8.7.0 + * Generates a {@link PrivateKey} object from the encoded {@code privateKey}string obtained from the + * {@code JKS} keystore. The provided {@link KeyStore} is designated at application boot time by the JVM's + * {@code javax.net.ssl.keyStore} and {@code javax.net.ssl.keyStorePassword} properties.s + * under the provided {@code alias} + * @param alias the alias to the private key stored in the YADA keystore + * @return the private key object + * @throws NoSuchAlgorithmException when the JKS keystore can't be loaded + * @throws KeyStoreException when there is no {@code JKS} keystore available + * @throws IOException when the JKS keystore can't be loaded due to a filesystem issue + * @throws CertificateException when the JKS keystore can't be loaded + * @throws UnrecoverableEntryException when the desired entry can't be extracted + * @since 8.7.0 */ - private static String getTimestamp() - { - return Long.toString((System.currentTimeMillis() / 1000)); - } - - /** - * Generates an OAuth 1.0 signature. This method originated at - * developer.pearson.com - * Oauth 1.0a Sample Code - * - * - * @param httpMethod The HTTP method of the request - * @param URL The request URL - * @param oauthParams The associative set of signable oAuth parameters - * @param requestBody The serialized POST/PUT message body - * @param secret Alphanumeric string used to validate the identity of the education partner (Private Key) - * - * @return A string containing the Base64-encoded signature digest - * - * @throws UnsupportedEncodingException - * @throws SignatureException - * @throws InvalidKeySpecException - * @throws NoSuchAlgorithmException - * @throws InvalidKeyException - * @since 8.7.0 - */ - private static String generateSignature( - String httpMethod, - URL url, - Map oauthParams, - byte[] requestBody, - String secret - ) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException + private PrivateKey getPrivateKey(String alias) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableEntryException { - // Ensure the HTTP Method is upper-cased - httpMethod = httpMethod.toUpperCase(); - - // Construct the URL-encoded OAuth parameter portion of the signature base string - String encodedParams = normalizeParams(httpMethod, url, oauthParams, requestBody); - - // URL-encode the relative URL - String encodedUri = URLEncoder.encode(url.getPath(), CHARSET_UTF8); - - // Build the signature base string to be signed with the Consumer Secret - String baseString = String.format("%s&%s&%s", httpMethod, encodedUri, encodedParams); - - return generateRSA(secret, baseString); - - } - - /** - * Normalizes all OAuth signable parameters and url query parameters according to OAuth 1.0. This method originated at - * developer.pearson.com - * Oauth 1.0a Sample Code - * - * - * @param httpMethod The upper-cased HTTP method - * @param URL The request URL - * @param oauthParams The associative set of signable oAuth parameters - * @param requstBody The serialized POST/PUT message body - * - * @return A string containing normalized and encoded oAuth parameters - * - * @throws UnsupportedEncodingException - * @since 8.7.0 - */ - private static String normalizeParams( - String httpMethod, - URL url, - Map oauthParams, - byte[] requestBody - ) throws UnsupportedEncodingException - { - - // Sort the parameters in lexicographical order, 1st by Key then by Value - Map kvpParams = new TreeMap(String.CASE_INSENSITIVE_ORDER); - kvpParams.putAll(oauthParams); - - // Place any query string parameters into a key value pair using equals ("=") to mark - // the key/value relationship and join each parameter with an ampersand ("&") - if (url.getQuery() != null) - { - for(String keyValue : url.getQuery().split(AMP)) - { - String[] p = keyValue.split(EQUAL); - kvpParams.put(p[0],p[1]); - } - - } - - // Include the body parameter if dealing with a POST or PUT request - if (YADARequest.METHOD_POST.equals(httpMethod) || YADARequest.METHOD_PUT.equals(httpMethod)) - { - String body = Base64.encodeBase64String(requestBody).replaceAll(CRLF, ""); - // url encode the body 2 times now before combining other params - body = URLEncoder.encode(body, CHARSET_UTF8); - body = URLEncoder.encode(body, CHARSET_UTF8); - kvpParams.put(BODY, body); - } - - // separate the key and values with a "=" - // separate the kvp with a "&" - StringBuilder combinedParams = new StringBuilder(); - String delimiter=""; - for(String key : kvpParams.keySet()) { - combinedParams.append(delimiter); - combinedParams.append(key); - combinedParams.append(EQUAL); - combinedParams.append(kvpParams.get(key)); - delimiter=AMP; - } - - // url encode the entire string again before returning - return URLEncoder.encode(combinedParams.toString(), CHARSET_UTF8); + String ksPath = System.getProperty(PROP_KEYSTORE); + char[] ksPass = System.getProperty(PROP_KEYSTORE_PASS).toCharArray(); + KeyStore.ProtectionParameter ksProtParam = new KeyStore.PasswordProtection(ksPass); + File ksFile = new File(ksPath); + URL ksURL = ksFile.toURI().toURL(); + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS); + try(InputStream is = ksURL.openStream()) + { + keyStore.load(is, null == ksPass ? null : ksPass); + } + KeyStore.PrivateKeyEntry ksEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, ksProtParam); + PrivateKey pk = ksEntry.getPrivateKey(); + + return pk; } -//TODO implement MAC support for Oauth 1.0a -// Leave the commented out MAC stuff in here for now. It's not pressing for the current requirement -// but should be rectified soon. It will require finding or building a provider in order to test, -// which could take some time. -// -// /** -// * Generates a Base64-encoded CMAC-AES digest -// * -// * @param key The secret key used to sign the data -// * @param msg The data to be signed -// * -// * @return A CMAC-AES hash -// * -// * @throws UnsupportedEncodingException -// */ -// private static String generateCmac(String key, String msg) throws UnsupportedEncodingException -// { -// byte[] keyBytes = key.getBytes(CHARSET_UTF8); -// byte[] data = msg.getBytes(CHARSET_UTF8); - -//// Person code: -//// CMac macProvider = new CMac(new AESFastEngine()); -//// macProvider.init(new KeyParameter(keyBytes)); -//// macProvider.reset(); -//// -//// macProvider.update(data, 0, data.length); -//// byte[] output = new byte[macProvider.getMacSize()]; -//// macProvider.doFinal(output, 0); - -// YADA stubby: -// Mac mac = Mac.getInstance("HmacSHA1"); -// mac.init(secretKey); -// -// // Convert the CMAC to a Base64 string and remove the new line the Base64 library adds -// String cmac = Base64.encodeBase64String(output).replaceAll("\r\n", ""); -// -// return cmac; -// -// } - - /** - * Generates a Base64-encoded RSA-SHA1 digest - * - * @param key The secret key used to sign the data - * @param msg The data to be signed - * - * @return An RSA-SHA1 hash - * - * @throws UnsupportedEncodingException - * @throws NoSuchAlgorithmException - * @throws InvalidKeySpecException - * @throws InvalidKeyException - * @throws SignatureException - */ - private static String generateRSA(String key, String msg) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException - { - byte[] keyBytes = key.getBytes(CHARSET_UTF8); - byte[] data = msg.getBytes(CHARSET_UTF8); - Signature signer = Signature.getInstance(ALGO_RSASHA1); - KeyFactory kf = KeyFactory.getInstance(ALGO_RSA); - PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); - signer.initSign(privateKey); - signer.update(data); - byte[] signed = signer.sign(); - return Base64.encodeBase64String(signed); - } /** - * This method relies on passage of {@link YADAQuery#getOAuth()} which returns + * Creates the {@link OAuthParameters} {@link HttpRequestInitializer}. + * This method relies on inclusion of {@link YADARequest#getOAuth()} which returns * a {@link JSONObject} containing: * *
    @@ -452,105 +312,82 @@ private static String generateRSA(String key, String msg) throws UnsupportedEnco *
  • oauth_token
  • *
  • oauth_verifier (secret)
  • *
  • oauth_version. For now, should always equal 1.0a
  • + *
  • oauth_private_key which is the alias + * under which the desired KeyStore.PrivateKeyEntry is stored
  • *
- * - * The logic of this method is derived from - * developer.pearson.com - * Oauth 1.0a Sample Code * - * @param body the value associated to the YADA_PAYLOAD key in the current "row" - * @param hConn the {@link HttpURLConnection} used to engage with the web service - * @throws SignatureException - * @throws InvalidKeySpecException - * @throws NoSuchAlgorithmException - * @throws InvalidKeyException - * @throws IOException + * @param url the transformed (conformed) REST endpoint + * @return the initializer to pass to the request factory + * @throws YADASecurityException if there is an issue with the keystore or key entry + * @throws YADAQueryConfigurationException if the 'oauth' request param is misconfigured * @since 8.7.0 */ - private void setAuthenticationOAuth(String body, HttpURLConnection hConn) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, IOException + private HttpRequestInitializer setAuthenticationOAuth(GenericUrl url) throws YADASecurityException, YADAQueryConfigurationException { - - String httpMethod = hConn.getRequestMethod(); - URL url = hConn.getURL(); - byte[] requestBody = null; - BufferedReader in = null; + OAuthRsaSigner signer = new OAuthRsaSigner(); - // Set the request body if making a POST or PUT request - //TODO handle body content--this is just YADA_PAYLOAD now which is probs totes wrong - if (YADARequest.METHOD_POST.equals(httpMethod) || YADARequest.METHOD_PUT.equals(httpMethod)) + try { - requestBody = body.getBytes(CHARSET_UTF8); - } - - // Create the OAuth parameter map from the oauth yada param - Map oauthParams = new LinkedHashMap(); - for(Object key : oauth.keySet()) + signer.privateKey = getPrivateKey(oauth.getString(OAUTH_PRIVATE_KEY)); + } + catch (NoSuchAlgorithmException|KeyStoreException|CertificateException|UnrecoverableEntryException|IOException e) + { + String msg = "There was a problem loading the KeyStore or obtaining the designated key."; + throw new YADASecurityException(msg,e); + } + catch (JSONException e) { - oauthParams.put((String)key, oauth.getString((String)key)); - } - oauthParams.put(OAUTH_TIMESTAMP, getTimestamp()); - oauthParams.put(OAUTH_NONCE, getNonce()); - + String msg = "There was a problem obtaining the private key entry alias from the request. Check the 'oauth' request parameter for the 'oauth_private_key' property."; + throw new YADAQueryConfigurationException(msg,e); + } + + OAuthParameters oauthParams = new OAuthParameters(); + oauthParams.consumerKey = oauth.getString(OAUTH_CONSUMER_KEY); + oauthParams.signer = signer; + oauthParams.signatureMethod = oauth.getString(OAUTH_SIGNATURE_METHOD); + oauthParams.token = oauth.getString(OAUTH_TOKEN); + oauthParams.verifier = oauth.getString(OAUTH_VERIFIER); + oauthParams.version = oauth.getString(OAUTH_VERSION); + oauthParams.computeNonce(); + oauthParams.computeTimestamp(); + try - // Get the OAuth 1.0 Signature - String secret = oauthParams.get(OAUTH_VERIFIER); - String signature = generateSignature(httpMethod, url, oauthParams, requestBody, secret); + { + oauthParams.computeSignature(this.method, url); + } + catch (GeneralSecurityException e) + { + String msg = "Unable to compute signature with the information provided. Check 'oauth' request parameter and key pair."; + throw new YADASecurityException(msg, e); + } - l.debug(String.format("OAuth 1.0 Signature = %s", signature)); - - // Add the oauth_signature parameter to the set of OAuth Parameters - oauthParams.put(OAUTH_SIGNATURE, signature); - - // Generate a string of comma delimited: keyName="URL-encoded(value)" pairs - StringBuilder paramStringBldr = new StringBuilder(); - String delimiter = ""; - for (String keyName : oauthParams.keySet()) { - paramStringBldr.append(delimiter); - String value = oauthParams.get((String) keyName); - paramStringBldr.append(keyName).append(EQUAL+QUOTE).append(URLEncoder.encode(value, CHARSET_UTF8)).append(QUOTE); - delimiter=COMMA; - } - - String urlString = url.toString(); - // omit the queryString from the url - int startOfQueryString = urlString.indexOf(URL_QUERY_MARKER); - if(startOfQueryString != -1) { - urlString = urlString.substring(0,startOfQueryString); - } - - // Build the X-Authorization request header - String xauth = String.format(OAUTH_REALM+EQUAL+QUOTE+"%s"+QUOTE+",%s", urlString, paramStringBldr.toString()); - l.debug(String.format("X-Authorization request header = %s", xauth)); - - // Add the header - hConn.addRequestProperty(X_AUTHORIZATION, xauth); + return oauthParams; } /** - * Sets the Authorization header for the request with Base64-encoded key/value pair - * @param url the URL object - * @param conn the URLConnection object + * Creates the {@link BasicAuthentication} {@link HttpRequestInitializer} + * @param url the conformed REST endpoint + * @return the initializer to pass to the request factory * @since 8.7.0 */ - private void setAuthenticatonBasic(URL url, URLConnection conn) + private HttpRequestInitializer setAuthenticatonBasic(GenericUrl url) { - //TODO basic auth and other auth methods should be mutually exclusive - if (url.getUserInfo() != null) - { - //TODO issue with '@' sign in pw, must decode first - String basicAuth = "Basic " + new String(new Base64().encode(url.getUserInfo().getBytes())); - conn.setRequestProperty("Authorization", basicAuth); - } + //TODO issue with '@' sign in pw, must decode first (is this still an issue?) + String[] userinfo = url.getUserInfo().split(":"); + String username = userinfo[0]; + String password = userinfo[1]; + BasicAuthentication ba = new BasicAuthentication(username, password); + return ba; } /** * Looks for cookie strings passed in the request or stored in the {@link YADAQuery} - * and adds them to the {@link URLConnection} + * and adds them to the {@link HttpRequest} * @param yq The YADAQuery object - * @param conn The URLConnection object + * @param request The HttpRequest object * @since 8.7.0 */ - private void setCookies(YADAQuery yq, URLConnection conn) + private void setCookies(YADAQuery yq, HttpRequest request) { if(yq.getCookies() != null && yq.getCookies().size() > 0) { @@ -559,21 +396,24 @@ private void setCookies(YADAQuery yq, URLConnection conn) { cookieStr += cookie.getName()+"="+cookie.getValue()+";"; } - conn.setRequestProperty("Cookie", cookieStr); + HttpHeaders headers = new HttpHeaders(); + headers.setCookie(cookieStr); + request.setHeaders(headers); } } /** * Looks for http header strings passed in the request or stored in the {@link YADAQuery} - * and adds them to the {@link URLConnection} - * @param yq the YADAQuery object - * @param conn the URLConnection object + * and adds them to the {@link HttpRequest} + * @param yq the {@link YADAQuery} object + * @param request the HttpRequest object * @since 8.7.0 */ - private void setHeaders(YADAQuery yq, URLConnection conn) + private void setHeaders(YADAQuery yq, HttpRequest request) { if(yq.getHttpHeaders() != null && yq.getHttpHeaders().length() > 0) { + HttpHeaders headers = new HttpHeaders(); l.debug("Processing custom headers..."); @SuppressWarnings("unchecked") Iterator keys = yq.getHttpHeaders().keys(); @@ -582,67 +422,46 @@ private void setHeaders(YADAQuery yq, URLConnection conn) String name = keys.next(); String value = yq.getHttpHeaders().getString(name); l.debug("Custom header: "+name+" : "+value); - conn.setRequestProperty(name, value); - if(name.equals(X_HTTP_METHOD_OVERRIDE) && value.equals(YADARequest.METHOD_PATCH)) - { - l.debug("Resetting method to ["+YADARequest.METHOD_POST+"]"); - this.method = YADARequest.METHOD_POST; - } + headers.set(name, value); } } } /** - * Injects the payload into the request's {@link OutputStream} if {@link YADARequest#getMethod()} returns - * {@link YADARequest#METHOD_POST}, {@link YADARequest#METHOD_PUT}, or {@link YADARequest#METHOD_PATCH} - * @param yq The current YADAQuery object - * @param row The current data "row" - * @param hConn The HTTPUrlConnection into which the data will be put - * @throws YADAAdaptorExecutionException in the event of an issue writing to the output stream. + * Returns true if method matches POST, PUT, or PATCH + * @param method the http method of the request to check + * @return true if method matches POST, PUT, or PATCH * @since 8.7.0 */ - private void handlePostPutPatch(YADAQuery yq, int row, HttpURLConnection hConn) throws YADAAdaptorExecutionException { - boolean isPostPutPatch = this.method.equals(YADARequest.METHOD_POST) - || this.method.equals(YADARequest.METHOD_PUT) - || this.method.equals(YADARequest.METHOD_PATCH); - - if(!this.method.equals(YADARequest.METHOD_GET)) - { - try - { - hConn.setRequestMethod(this.method); - if(isPostPutPatch) - { - //TODO make YADA_PAYLOAD case-insensitive and create an alias for it, e.g., ypl - // NOTE: YADA_PAYLOAD is a COLUMN NAME found in a JSONParams DATA object. It - // is not a YADA param - String payload = yq.getDataRow(row).get(YADA_PAYLOAD)[0]; - hConn.setDoOutput(true); - OutputStreamWriter writer; - writer = new OutputStreamWriter(hConn.getOutputStream()); - writer.write(payload.toString()); - writer.flush(); - } - } - catch (IOException e) - { - String msg = "There was a problem injecting the payload into the request"; - throw new YADAAdaptorExecutionException(msg, e); - } - } + protected static boolean isPostPutPatch(String method) + { + return method.equals(YADARequest.METHOD_POST) + || method.equals(YADARequest.METHOD_PUT) + || method.equals(YADARequest.METHOD_PATCH); } /** * Reads the response {@link InputStream} and writes it to a {@link String} - * @param hConn the connection object + * @param request the request object * @return a String containing the response data * @throws YADAAdaptorExecutionException when reading the input stream fails * @since 8.7.0 */ - private String processResponse(HttpURLConnection hConn) throws YADAAdaptorExecutionException + private String processResponse(HttpRequest request) throws YADAAdaptorExecutionException { String result = ""; - try(BufferedReader in = new BufferedReader(new InputStreamReader(hConn.getInputStream()))) + HttpResponse response; + try + { + response = request.execute(); + } + catch (IOException e) + { + String msg = "Unable to execute request."; + throw new YADAAdaptorExecutionException(msg,e); + } + + try(BufferedReader in = new BufferedReader(new InputStreamReader(response.getContent()))) { String inputLine; while ((inputLine = in.readLine()) != null) @@ -659,114 +478,149 @@ private String processResponse(HttpURLConnection hConn) throws YADAAdaptorExecut } /** - * Writes the header fields to the console or log - * @param conn the current connection object + * Writes the header fields to the console or log while in DEBUG mode + * @param request the current request object * @since 8.7.0 */ - private void logRequest(URLConnection conn) { - Map> map = conn.getHeaderFields(); - for (Map.Entry> entry : map.entrySet()) { + private void logRequest(HttpRequest request) { + Map map = request.getHeaders(); + for (Entry entry : map.entrySet()) { l.debug("Key : " + entry.getKey() + " ,Value : " + entry.getValue()); } } + /** + * Wrapper for {@link HttpRequestFactory#buildRequest(String, GenericUrl, HttpContent)} + * @param yq The {@link YADAQuery} defined by the {@link YADARequest} + * @param payload the request body or null + * @param url the REST endpoint + * @return the initialized {@link HttpRequest} + * @throws YADAQueryConfigurationException when there is an issue with request construction + * @throws YADASecurityException when authentication fails + */ + private HttpRequest buildRequest(YADAQuery yq, String payload, GenericUrl url) throws YADAQueryConfigurationException, YADASecurityException + { + HttpContent content = null; + HttpRequestInitializer initializer = setAuthentication(yq, url); + HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(initializer); + if(isPostPutPatch(this.method)) + { + content = ByteArrayContent.fromString(null, payload); + if(this.method.equals(YADARequest.METHOD_PATCH)) + this.method = YADARequest.METHOD_PUT; + } + + try + { + return requestFactory.buildRequest(this.method, url, content); + } + catch (IOException e) + { + String msg = "Unable to initialize the "+this.method+" request for ["+url+"]"; + throw new YADAQueryConfigurationException(msg,e); + } + } + /** * Gets the input stream from the {@link URLConnection} and stores it in * the {@link YADAQueryResult} in {@code yq} + * @throws YADASecurityException if authentication fails * @see com.novartis.opensource.yada.adaptor.Adaptor#execute(com.novartis.opensource.yada.YADAQuery) */ @Override - public void execute(YADAQuery yq) throws YADAAdaptorExecutionException + public void execute(YADAQuery yq) throws YADAAdaptorExecutionException, YADASecurityException { + // + // THINGS ORGANIZED IN HERE: + // + // Type of request + // This will determine the 'build' method in the HttpRequestFactory (i.e., buildGet, buildPost, etc) + // + // Type of authentication + // Basic, Oauth, None + // + // Post content + // Cookies + // Headers + // Proxy (config'd in JVM for now) + // + + // 1) Override method type if necessary + if(yq.getHttpHeaders().has(H_X_HTTP_METHOD_OVERRIDE)) + { + this.method = yq.getHttpHeaders().getString(H_X_HTTP_METHOD_OVERRIDE); + l.debug("Resetting method to ["+this.method+"]"); + } + + // 2) set 'count' to false until the algo for counting rest response rows is impl. resetCountParameter(yq); + + // 3) get the data array from the YADAQuery + // + // Remember: + // A row is a set of YADA URL parameter values, e.g., + // + // x,y,z in this: + // ...yada/q/queryname/p/x,y,z + // so 1 row + // + // or each of {col1:x,col2:y,col3:z} and {col1:a,col2:b,col3:c} in this: + // ...j=[{qname:queryname,DATA:[{col1:x,col2:y,col3:z},{col1:a,col2:b,col3:c}]}] + // so 2 rows + // int rows = yq.getData().size() > 0 ? yq.getData().size() : 1; - /* - * Remember: - * A row is an set of YADA URL parameter values, e.g., - * - * x,y,z in this: - * ...yada/q/queryname/p/x,y,z - * so 1 row - * - * or each of {col1:x,col2:y,col3:z} and {col1:a,col2:b,col3:c} in this: - * ...j=[{qname:queryname,DATA:[{col1:x,col2:y,col3:z},{col1:a,col2:b,col3:c}]}] - * so 2 rows - */ - URL url; - URLConnection conn; - HttpURLConnection hConn = null; + + + // 4) process the url + String urlString; + HttpRequest request = null; + for(int row=0;row