X-Git-Url: https://gerrit.onap.org/r/gitweb?p=ccsdk%2Fsli%2Fplugins.git;a=blobdiff_plain;f=restapi-call-node%2Fprovider%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Fccsdk%2Fsli%2Fplugins%2Frestapicall%2FRestapiCallNode.java;h=b4d7e1ba8950d81929b74ef0a1dc6b5eabb9bba8;hp=83b12c767f60c163f4b11e0a49953e84dca8a00f;hb=refs%2Fheads%2Fmaster;hpb=0454460bbb22be0cd2a0ed998f2532c371a90119 diff --git a/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java b/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java old mode 100644 new mode 100755 index 83b12c76..3d704249 --- a/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java +++ b/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java @@ -25,23 +25,33 @@ package org.onap.ccsdk.sli.plugins.restapicall; import static java.lang.Boolean.valueOf; import static javax.ws.rs.client.Entity.entity; import static org.onap.ccsdk.sli.plugins.restapicall.AuthType.fromString; - +import java.io.BufferedReader; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.ProtocolException; import java.net.SocketException; import java.net.URI; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyStore; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -53,118 +63,179 @@ import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.Feature; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import org.apache.commons.lang3.StringUtils; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.glassfish.jersey.client.oauth1.ConsumerCredentials; import org.glassfish.jersey.client.oauth1.OAuth1ClientSupport; +import org.glassfish.jersey.media.multipart.MultiPart; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.media.multipart.file.FileDataBodyPart; import org.onap.ccsdk.sli.core.sli.SvcLogicContext; import org.onap.ccsdk.sli.core.sli.SvcLogicException; import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin; +import org.onap.logging.filter.base.HttpURLConnectionMetricUtil; +import org.onap.logging.filter.base.MetricLogClientFilter; +import org.onap.logging.filter.base.ONAPComponents; +import org.onap.logging.ref.slf4j.ONAPLogConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; public class RestapiCallNode implements SvcLogicJavaPlugin { - protected static final String DME2_PROPERTIES_FILE_NAME = "dme2.properties"; + protected static final String PARTNERS_FILE_NAME = "partners.json"; protected static final String UEB_PROPERTIES_FILE_NAME = "ueb.properties"; protected static final String DEFAULT_PROPERTIES_DIR = "/opt/onap/ccsdk/data/properties"; protected static final String PROPERTIES_DIR_KEY = "SDNC_CONFIG_DIR"; + protected static final int DEFAULT_HTTP_CONNECT_TIMEOUT_MS = 30000; // 30 seconds + protected static final int DEFAULT_HTTP_READ_TIMEOUT_MS = 600000; // 10 minutes private static final Logger log = LoggerFactory.getLogger(RestapiCallNode.class); - protected RetryPolicyStore retryPolicyStore; private String uebServers; private String defaultUebTemplateFileName = "/opt/bvc/restapi/templates/default-ueb-message.json"; private String responseReceivedMessage = "Response received. Time: {}"; private String responseHttpCodeMessage = "HTTP response code: {}"; private String requestPostingException = "Exception while posting http request to client "; - private static String skipSendingMessage = "skipSending"; - private static String responsePrefix = "responsePrefix"; - private static String restapiUrlString = "restapiUrl"; + protected static final String skipSendingMessage = "skipSending"; + protected static final String responsePrefix = "responsePrefix"; + protected static final String restapiUrlString = "restapiUrl"; + protected static final String restapiUserKey = "restapiUser"; + protected static final String restapiPasswordKey = "restapiPassword"; + protected Integer httpConnectTimeout; + protected Integer httpReadTimeout; + + protected HashMap partnerStore; + private static final Pattern retryPattern = Pattern.compile(".*,(http|https):.*"); public RestapiCallNode() { String configDir = System.getProperty(PROPERTIES_DIR_KEY, DEFAULT_PROPERTIES_DIR); - - try (FileInputStream in = new FileInputStream(configDir + "/" + DME2_PROPERTIES_FILE_NAME)) { - Properties props = new Properties(); - props.load(in); - this.retryPolicyStore = new RetryPolicyStore(); - this.retryPolicyStore.setProxyServers(props.getProperty("proxyUrl")); - log.info("DME2 support enabled"); + try { + String jsonString = readFile(configDir + "/" + PARTNERS_FILE_NAME); + JSONObject partners = new JSONObject(jsonString); + partnerStore = new HashMap<>(); + loadPartners(partners); + log.info("Partners support enabled"); } catch (Exception e) { - log.warn("DME2 properties could not be read, DME2 support will not be enabled.", e); + log.warn("Partners file could not be read, Partner support will not be enabled. " + e.getMessage()); } try (FileInputStream in = new FileInputStream(configDir + "/" + UEB_PROPERTIES_FILE_NAME)) { Properties props = new Properties(); props.load(in); - this.uebServers = props.getProperty("servers"); + uebServers = props.getProperty("servers"); log.info("UEB support enabled"); } catch (Exception e) { - log.warn("UEB properties could not be read, UEB support will not be enabled.", e); + log.warn("UEB properties could not be read, UEB support will not be enabled. " + e.getMessage()); } + httpConnectTimeout = readOptionalInteger("HTTP_CONNECT_TIMEOUT_MS",DEFAULT_HTTP_CONNECT_TIMEOUT_MS); + httpReadTimeout = readOptionalInteger("HTTP_READ_TIMEOUT_MS",DEFAULT_HTTP_READ_TIMEOUT_MS); + } + + @SuppressWarnings("unchecked") + protected void loadPartners(JSONObject partners) { + Iterator keys = partners.keys(); + String partnerUserKey = "user"; + String partnerPasswordKey = "password"; + String partnerUrlKey = "url"; + + while (keys.hasNext()) { + String partnerKey = keys.next(); + try { + JSONObject partnerObject = (JSONObject) partners.get(partnerKey); + if (partnerObject.has(partnerUserKey) && partnerObject.has(partnerPasswordKey)) { + String url = null; + if (partnerObject.has(partnerUrlKey)) { + url = partnerObject.getString(partnerUrlKey); + } + String userName = partnerObject.getString(partnerUserKey); + String password = partnerObject.getString(partnerPasswordKey); + PartnerDetails details = new PartnerDetails(userName, getObfuscatedVal(password), url); + partnerStore.put(partnerKey, details); + log.info("mapped partner using partner key " + partnerKey); + } else { + log.info("Partner " + partnerKey + " is missing required keys, it won't be mapped"); + } + } catch (JSONException e) { + log.info("Couldn't map the partner using partner key " + partnerKey, e); + } + } + } + + /* Unobfuscate param value */ + private static String getObfuscatedVal(String paramValue) { + String resValue = paramValue; + if (paramValue != null && paramValue.startsWith("${") && paramValue.endsWith("}")) + { + String paramStr = paramValue.substring(2, paramValue.length()-1); + if (paramStr != null && paramStr.length() > 0) + { + String val = System.getenv(paramStr); + if (val != null && val.length() > 0) + { + resValue=val; + log.info("Obfuscated value RESET for param value:" + paramValue); + } + } + } + return resValue; } /** * Returns parameters from the parameter map. * * @param paramMap parameter map - * @param p parameters instance + * @param p parameters instance * @return parameters filed instance * @throws SvcLogicException when svc logic exception occurs */ - public static Parameters getParameters(Map paramMap, - Parameters p) - throws SvcLogicException { - p.templateFileName = parseParam(paramMap, "templateFileName", - false, null); + public static Parameters getParameters(Map paramMap, Parameters p) throws SvcLogicException { + + p.templateFileName = parseParam(paramMap, "templateFileName", false, null); p.requestBody = parseParam(paramMap, "requestBody", false, null); p.restapiUrl = parseParam(paramMap, restapiUrlString, true, null); + p.restapiUrlSuffix = parseParam(paramMap, "restapiUrlSuffix", false, null); + if (p.restapiUrlSuffix != null) { + p.restapiUrl = p.restapiUrl + p.restapiUrlSuffix; + } + + p.restapiUrl = UriBuilder.fromUri(p.restapiUrl).toTemplate(); validateUrl(p.restapiUrl); - p.restapiUser = parseParam(paramMap, "restapiUser", false, null); - p.restapiPassword = parseParam(paramMap, "restapiPassword", false, - null); - p.oAuthConsumerKey = parseParam(paramMap, "oAuthConsumerKey", - false, null); - p.oAuthConsumerSecret = parseParam(paramMap, "oAuthConsumerSecret", - false, null); - p.oAuthSignatureMethod = parseParam(paramMap, "oAuthSignatureMethod", - false, null); + + p.restapiUser = parseParam(paramMap, restapiUserKey, false, null); + p.restapiPassword = parseParam(paramMap, restapiPasswordKey, false, null); + p.oAuthConsumerKey = parseParam(paramMap, "oAuthConsumerKey", false, null); + p.oAuthConsumerSecret = parseParam(paramMap, "oAuthConsumerSecret", false, null); + p.oAuthSignatureMethod = parseParam(paramMap, "oAuthSignatureMethod", false, null); p.oAuthVersion = parseParam(paramMap, "oAuthVersion", false, null); p.contentType = parseParam(paramMap, "contentType", false, null); - p.format = Format.fromString(parseParam(paramMap, "format", false, - "json")); - p.authtype = fromString(parseParam(paramMap, "authType", false, - "unspecified")); - p.httpMethod = HttpMethod.fromString(parseParam(paramMap, "httpMethod", - false, "post")); + p.format = Format.fromString(parseParam(paramMap, "format", false, "json")); + p.authtype = fromString(parseParam(paramMap, "authType", false, "unspecified")); + p.httpMethod = HttpMethod.fromString(parseParam(paramMap, "httpMethod", false, "post")); p.responsePrefix = parseParam(paramMap, responsePrefix, false, null); p.listNameList = getListNameList(paramMap); String skipSendingStr = paramMap.get(skipSendingMessage); p.skipSending = "true".equalsIgnoreCase(skipSendingStr); - p.convertResponse = valueOf(parseParam(paramMap, "convertResponse", - false, "true")); - p.trustStoreFileName = parseParam(paramMap, "trustStoreFileName", - false, null); - p.trustStorePassword = parseParam(paramMap, "trustStorePassword", - false, null); - p.keyStoreFileName = parseParam(paramMap, "keyStoreFileName", - false, null); - p.keyStorePassword = parseParam(paramMap, "keyStorePassword", - false, null); - p.ssl = p.trustStoreFileName != null && p.trustStorePassword != null - && p.keyStoreFileName != null && p.keyStorePassword != null; - p.customHttpHeaders = parseParam(paramMap, "customHttpHeaders", - false, null); + p.convertResponse = valueOf(parseParam(paramMap, "convertResponse", false, "true")); + p.keyStoreFileName = parseParam(paramMap, "keyStoreFileName", false, null); + p.keyStorePassword = parseParam(paramMap, "keyStorePassword", false, null); + p.ssl = p.keyStoreFileName != null && p.keyStorePassword != null; + p.customHttpHeaders = parseParam(paramMap, "customHttpHeaders", false, null); p.partner = parseParam(paramMap, "partner", false, null); - p.dumpHeaders = valueOf(parseParam(paramMap, "dumpHeaders", - false, null)); - p.returnRequestPayload = valueOf(parseParam( - paramMap, "returnRequestPayload", false, null)); + p.dumpHeaders = valueOf(parseParam(paramMap, "dumpHeaders", false, null)); + p.returnRequestPayload = valueOf(parseParam(paramMap, "returnRequestPayload", false, null)); + p.accept = parseParam(paramMap, "accept", false, null); + p.multipartFormData = valueOf(parseParam(paramMap, "multipartFormData", false, "false")); + p.multipartFile = parseParam(paramMap, "multipartFile", false, null); + p.targetEntity = parseParam(paramMap, "targetEntity", false, null); return p; } @@ -174,13 +245,18 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { * @param restapiUrl rest api URL * @throws SvcLogicException when URL validation fails */ - private static void validateUrl(String restapiUrl) - throws SvcLogicException { - try { - URI.create(restapiUrl); - } catch (IllegalArgumentException e) { - throw new SvcLogicException("Invalid input of url " - + e.getLocalizedMessage(), e); + private static void validateUrl(String restapiUrl) throws SvcLogicException { + if (containsMultipleUrls(restapiUrl)) { + String[] urls = getMultipleUrls(restapiUrl); + for (String url : urls) { + validateUrl(url); + } + } else { + try { + URI.create(restapiUrl); + } catch (IllegalArgumentException e) { + throw new SvcLogicException("Invalid input of url " + e.getLocalizedMessage(), e); + } } } @@ -201,18 +277,17 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } /** - * Parses the parameter string map of property, validates if required, - * assigns default value if present and returns the value. + * Parses the parameter string map of property, validates if required, assigns default value if + * present and returns the value. * * @param paramMap string param map - * @param name name of the property + * @param name name of the property * @param required if value required - * @param def default value + * @param def default value * @return value of the property * @throws SvcLogicException if required parameter value is empty */ - public static String parseParam(Map paramMap, String name, - boolean required, String def) + public static String parseParam(Map paramMap, String name, boolean required, String def) throws SvcLogicException { String s = paramMap.get(name); @@ -247,45 +322,148 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } value.append(s.substring(i)); - log.info("Parameter {}: [{}]", name, value); - return value.toString(); - } + log.info("Parameter {}: [{}]", name, maskPassword(name, value)); - public RetryPolicyStore getRetryPolicyStore() { - return retryPolicyStore; + return value.toString(); } - public void setRetryPolicyStore(RetryPolicyStore retryPolicyStore) { - this.retryPolicyStore = retryPolicyStore; + private static Object maskPassword(String name, Object value) { + String[] pwdNames = {"pwd", "passwd", "password", "Pwd", "Passwd", "Password"}; + for (String pwdName : pwdNames) { + if (name.contains(pwdName)) { + return "**********"; + } + } + return value; } /** - * Allows Directed Graphs the ability to interact with REST APIs. + * Allows Directed Graphs the ability to interact with REST APIs. + * * @param paramMap HashMap of parameters passed by the DG to this function - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * vpn-information.vrf-details - * - * - * - * - * - * - * - *
parameterMandatory/Optionaldescriptionexample values
templateFileNameOptionalfull path to template file that can be used to build a request/sdncopt/bvc/restapi/templates/vnf_service-configuration-operation_minimal.json
restapiUrlMandatoryurl to send the request tohttps://sdncodl:8543/restconf/operations/L3VNF-API:create-update-vnf-request
restapiUserOptionaluser name to use for http basic authenticationsdnc_ws
restapiPasswordOptionalunencrypted password to use for http basic authenticationplain_password
oAuthConsumerKeyOptionalConsumer key to use for http oAuth authenticationplain_key
oAuthConsumerSecretOptionalConsumer secret to use for http oAuth authenticationplain_secret
oAuthSignatureMethodOptionalConsumer method to use for http oAuth authenticationmethod
oAuthVersionOptionalVersion http oAuth authenticationversion
contentTypeOptionalhttp content type to set in the http headerusually application/json or application/xml
formatOptionalshould match request body formatjson or xml
httpMethodOptionalhttp method to use when sending the requestget post put delete patch
responsePrefixOptionallocation the response will be written to in context memorytmp.restapi.result
listName[i]OptionalUsed for processing XML responses with repeating elements.
skipSendingOptionaltrue or false
convertResponse Optionalwhether the response should be convertedtrue or false
customHttpHeadersOptionala list additional http headers to be passed in, follow the format in the exampleX-CSI-MessageId=messageId,headerFieldName=headerFieldValue
dumpHeadersOptionalwhen true writes http header content to context memorytrue or false
partnerOptionalneeded for DME2 callsdme2proxy
returnRequestPayloadOptionalused to return payload built in the requesttrue or false
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * vpn-information.vrf-details + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
parameterMandatory/Optionaldescriptionexample values
templateFileNameOptionalfull path to template file that can be used to build a request/sdncopt/bvc/restapi/templates/vnf_service-configuration-operation_minimal.json
restapiUrlMandatoryurl to send the request tohttps://sdncodl:8543/restconf/operations/L3VNF-API:create-update-vnf-request
restapiUserOptionaluser name to use for http basic authenticationsdnc_ws
restapiPasswordOptionalunencrypted password to use for http basic authenticationplain_password
oAuthConsumerKeyOptionalConsumer key to use for http oAuth authenticationplain_key
oAuthConsumerSecretOptionalConsumer secret to use for http oAuth authenticationplain_secret
oAuthSignatureMethodOptionalConsumer method to use for http oAuth authenticationmethod
oAuthVersionOptionalVersion http oAuth authenticationversion
contentTypeOptionalhttp content type to set in the http headerusually application/json or application/xml
formatOptionalshould match request body formatjson or xml
httpMethodOptionalhttp method to use when sending the requestget post put delete patch
responsePrefixOptionallocation the response will be written to in context memorytmp.restapi.result
listName[i]OptionalUsed for processing XML responses with repeating + * elements.
skipSendingOptionaltrue or false
convertResponseOptionalwhether the response should be convertedtrue or false
customHttpHeadersOptionala list additional http headers to be passed in, follow the format in the exampleX-CSI-MessageId=messageId,headerFieldName=headerFieldValue
dumpHeadersOptionalwhen true writes http header content to context memorytrue or false
partnerOptionalused to retrieve username, password and url if partner store existsaaf
returnRequestPayloadOptionalused to return payload built in the requesttrue or false
* @param ctx Reference to context memory * @throws SvcLogicException * @since 11.0.2 @@ -295,15 +473,20 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { sendRequest(paramMap, ctx, null); } - public void sendRequest(Map paramMap, SvcLogicContext ctx, Integer retryCount) + protected void sendRequest(Map paramMap, SvcLogicContext ctx, RetryPolicy retryPolicy) throws SvcLogicException { - RetryPolicy retryPolicy = null; - HttpResponse r = new HttpResponse(); + HttpResponse r = new HttpResponse(); try { + handlePartner(paramMap); Parameters p = getParameters(paramMap, new Parameters()); - if (p.partner != null) { - retryPolicy = retryPolicyStore.getRetryPolicy(p.partner); + if(p.targetEntity != null && !p.targetEntity.isEmpty()) { + MDC.put(ONAPLogConstants.MDCs.TARGET_ENTITY, p.targetEntity); + } + if (containsMultipleUrls(p.restapiUrl) && retryPolicy == null) { + String[] urls = getMultipleUrls(p.restapiUrl); + retryPolicy = new RetryPolicy(urls, urls.length * 2); + p.restapiUrl = urls[0]; } String pp = p.responsePrefix != null ? p.responsePrefix + '.' : ""; @@ -356,36 +539,22 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { if (retryPolicy == null || !shouldRetry) { setFailureResponseStatus(ctx, prefix, e.getMessage(), r); } else { - if (retryCount == null) { - retryCount = 0; - } - String retryMessage = retryCount + " attempts were made out of " + retryPolicy.getMaximumRetries() + - " maximum retries."; - log.debug(retryMessage); + log.debug(retryPolicy.getRetryMessage()); try { - retryCount = retryCount + 1; - if (retryCount < retryPolicy.getMaximumRetries() + 1) { - URI uri = new URI(paramMap.get(restapiUrlString)); - String hostname = uri.getHost(); - String retryString = retryPolicy.getNextHostName(uri.toString()); - URI uriTwo = new URI(retryString); - URI retryUri = UriBuilder.fromUri(uri).host(uriTwo.getHost()).port(uriTwo.getPort()).scheme( - uriTwo.getScheme()).build(); - paramMap.put(restapiUrlString, retryUri.toString()); - log.debug("URL was set to {}", retryUri.toString()); - log.debug("Failed to communicate with host {}. Request will be re-attempted using the host {}.", - hostname, retryString); - log.debug("This is retry attempt {} out of {}", retryCount, retryPolicy.getMaximumRetries()); - sendRequest(paramMap, ctx, retryCount); + // calling getNextHostName increments the retry count so it should be called before shouldRetry + String retryString = retryPolicy.getNextHostName(); + if (retryPolicy.shouldRetry()) { + paramMap.put(restapiUrlString, retryString); + log.debug("retry attempt {} will use the retry url {}", retryPolicy.getRetryCount(), + retryString); + sendRequest(paramMap, ctx, retryPolicy); } else { - log.debug("Maximum retries reached, calling setFailureResponseStatus."); + log.debug("Maximum retries reached, won't attempt to retry. Calling setFailureResponseStatus."); setFailureResponseStatus(ctx, prefix, e.getMessage(), r); } } catch (Exception ex) { - log.error("Could not attempt retry.", ex); - String retryErrorMessage = - "Retry attempt has failed. No further retry shall be attempted, calling " + - "setFailureResponseStatus."; + String retryErrorMessage = "Retry attempt " + retryPolicy.getRetryCount() + + "has failed with error message " + ex.getMessage(); setFailureResponseStatus(ctx, prefix, retryErrorMessage, r); } } @@ -396,8 +565,19 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } } - protected String buildXmlJsonRequest(SvcLogicContext ctx, String template, Format format) - throws SvcLogicException { + protected void handlePartner(Map paramMap) { + String partner = paramMap.get("partner"); + if (partner != null && partner.length() > 0) { + PartnerDetails details = partnerStore.get(partner); + paramMap.put(restapiUserKey, details.username); + paramMap.put(restapiPasswordKey, details.password); + if (paramMap.get(restapiUrlString) == null) { + paramMap.put(restapiUrlString, details.url); + } + } + } + + protected String buildXmlJsonRequest(SvcLogicContext ctx, String template, Format format) throws SvcLogicException { log.info("Building {} started", format); long t1 = System.currentTimeMillis(); String originalTemplate = template; @@ -446,15 +626,15 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } } - String req = format == Format.XML - ? XmlJsonUtil.removeEmptyStructXml(ss.toString()) : XmlJsonUtil.removeEmptyStructJson(originalTemplate, ss.toString()); + String req = format == Format.XML ? XmlJsonUtil.removeEmptyStructXml(ss.toString()) + : XmlJsonUtil.removeEmptyStructJson(originalTemplate, ss.toString()); if (format == Format.JSON) { req = XmlJsonUtil.removeLastCommaJson(req); } long t2 = System.currentTimeMillis(); - log.info("Building {} completed. Time: {}", format, (t2 - t1)); + log.info("Building {} completed. Time: {}", format, t2 - t1); return req; } @@ -554,12 +734,12 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { if (p.authtype == AuthType.Unspecified) { if (p.restapiUser != null && p.restapiPassword != null) { client.register(HttpAuthenticationFeature.basic(p.restapiUser, p.restapiPassword)); - } else if (p.oAuthConsumerKey != null && p.oAuthConsumerSecret != null - && p.oAuthSignatureMethod != null) { - Feature oAuth1Feature = OAuth1ClientSupport - .builder(new ConsumerCredentials(p.oAuthConsumerKey, p.oAuthConsumerSecret)) - .version(p.oAuthVersion).signatureMethod(p.oAuthSignatureMethod).feature().build(); + } else if (p.oAuthConsumerKey != null && p.oAuthConsumerSecret != null && p.oAuthSignatureMethod != null) { + Feature oAuth1Feature = + OAuth1ClientSupport.builder(new ConsumerCredentials(p.oAuthConsumerKey, p.oAuthConsumerSecret)) + .version(p.oAuthVersion).signatureMethod(p.oAuthSignatureMethod).feature().build(); client.register(oAuth1Feature); + } } else { if (p.authtype == AuthType.DIGEST) { @@ -567,16 +747,18 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { client.register(HttpAuthenticationFeature.digest(p.restapiUser, p.restapiPassword)); } else { throw new SvcLogicException( - "oAUTH authentication type selected but all restapiUser and restapiPassword " + - "parameters doesn't exist", new Throwable()); + "oAUTH authentication type selected but all restapiUser and restapiPassword " + + "parameters doesn't exist", + new Throwable()); } } else if (p.authtype == AuthType.BASIC) { if (p.restapiUser != null && p.restapiPassword != null) { client.register(HttpAuthenticationFeature.basic(p.restapiUser, p.restapiPassword)); } else { throw new SvcLogicException( - "oAUTH authentication type selected but all restapiUser and restapiPassword " + - "parameters doesn't exist", new Throwable()); + "oAUTH authentication type selected but all restapiUser and restapiPassword " + + "parameters doesn't exist", + new Throwable()); } } else if (p.authtype == AuthType.OAUTH) { if (p.oAuthConsumerKey != null && p.oAuthConsumerSecret != null && p.oAuthSignatureMethod != null) { @@ -586,8 +768,9 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { client.register(oAuth1Feature); } else { throw new SvcLogicException( - "oAUTH authentication type selected but all oAuthConsumerKey, oAuthConsumerSecret " + - "and oAuthSignatureMethod parameters doesn't exist", new Throwable()); + "oAUTH authentication type selected but all oAuthConsumerKey, oAuthConsumerSecret " + + "and oAuthSignatureMethod parameters doesn't exist", + new Throwable()); } } } @@ -598,47 +781,47 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { * Receives the http response for the http request sent. * * @param request request msg - * @param p parameters + * @param p parameters * @return HTTP response * @throws SvcLogicException when sending http request fails */ - public HttpResponse sendHttpRequest(String request, Parameters p) - throws SvcLogicException { + public HttpResponse sendHttpRequest(String request, Parameters p) throws SvcLogicException { SSLContext ssl = null; if (p.ssl && p.restapiUrl.startsWith("https")) { ssl = createSSLContext(p); } Client client; - if (ssl != null) { HttpsURLConnection.setDefaultSSLSocketFactory(ssl.getSocketFactory()); - client = ClientBuilder.newBuilder().sslContext(ssl).hostnameVerifier((s, sslSession) -> true) - .build(); + client = ClientBuilder.newBuilder().sslContext(ssl).hostnameVerifier((s, sslSession) -> true).build(); } else { - client = ClientBuilder.newBuilder().hostnameVerifier((s, sslSession) -> true) - .build(); + client = ClientBuilder.newBuilder().hostnameVerifier((s, sslSession) -> true).build(); } - client.property(ClientProperties.CONNECT_TIMEOUT, 5000); + setClientTimeouts(client); + // Needed to support additional HTTP methods such as PATCH + client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); + client.register(new MetricLogClientFilter()); WebTarget webTarget = addAuthType(client, p).target(p.restapiUrl); - log.info("Sending request:"); - log.info(request); long t1 = System.currentTimeMillis(); HttpResponse r = new HttpResponse(); r.code = 200; + String accept = p.accept; + if (accept == null) { + accept = p.format == Format.XML ? "application/xml" : "application/json"; + } - if (!p.skipSending) { - String tt = p.format == Format.XML ? "application/xml" : "application/json"; - String tt1 = tt + ";charset=UTF-8"; - if (p.contentType != null) { - tt = p.contentType; - tt1 = p.contentType; - } + String contentType = p.contentType; + if (contentType == null) { + contentType = accept + ";charset=UTF-8"; + } - Invocation.Builder invocationBuilder = webTarget.request(tt1).accept(tt); + if (!p.skipSending && !p.multipartFormData) { + + Invocation.Builder invocationBuilder = webTarget.request(contentType).accept(accept); if (p.format == Format.NONE) { invocationBuilder.header("", ""); @@ -653,15 +836,67 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } } - invocationBuilder.header("X-ECOMP-RequestID", org.slf4j.MDC.get("X-ECOMP-RequestID")); + invocationBuilder.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); + + Response response; + + try { + // When the HTTP operation has no body do not set the content-type + //setting content-type has caused errors with some servers when no body is present + if (request == null) { + response = invocationBuilder.method(p.httpMethod.toString()); + } else { + log.info("Sending request below to url " + p.restapiUrl); + log.info(request); + response = invocationBuilder.method(p.httpMethod.toString(), entity(request, contentType)); + } + } catch (ProcessingException | IllegalStateException e) { + throw new SvcLogicException(requestPostingException + e.getLocalizedMessage(), e); + } + + r.code = response.getStatus(); + r.headers = response.getStringHeaders(); + EntityTag etag = response.getEntityTag(); + if (etag != null) { + r.message = etag.getValue(); + } + if (response.hasEntity() && r.code != 204) { + r.body = response.readEntity(String.class); + } + } else if (!p.skipSending && p.multipartFormData) { + + WebTarget wt = client.register(MultiPartFeature.class).target(p.restapiUrl); + + MultiPart multiPart = new MultiPart(); + multiPart.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE); + + FileDataBodyPart fileDataBodyPart = + new FileDataBodyPart("file", new File(p.multipartFile), MediaType.APPLICATION_OCTET_STREAM_TYPE); + multiPart.bodyPart(fileDataBodyPart); + + + Invocation.Builder invocationBuilder = wt.request(contentType).accept(accept); + + if (p.format == Format.NONE) { + invocationBuilder.header("", ""); + } + + if (p.customHttpHeaders != null && p.customHttpHeaders.length() > 0) { + String[] keyValuePairs = p.customHttpHeaders.split(","); + for (String singlePair : keyValuePairs) { + int equalPosition = singlePair.indexOf('='); + invocationBuilder.header(singlePair.substring(0, equalPosition), + singlePair.substring(equalPosition + 1, singlePair.length())); + } + } Response response; try { - response = invocationBuilder.method(p.httpMethod.toString(), entity(request, tt1)); + response = + invocationBuilder.method(p.httpMethod.toString(), entity(multiPart, multiPart.getMediaType())); } catch (ProcessingException | IllegalStateException e) { - throw new SvcLogicException(requestPostingException + - e.getLocalizedMessage(), e); + throw new SvcLogicException(requestPostingException + e.getLocalizedMessage(), e); } r.code = response.getStatus(); @@ -673,10 +908,11 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { if (response.hasEntity() && r.code != 204) { r.body = response.readEntity(String.class); } + } long t2 = System.currentTimeMillis(); - log.info(responseReceivedMessage, (t2 - t1)); + log.info(responseReceivedMessage, t2 - t1); log.info(responseHttpCodeMessage, r.code); log.info("HTTP response message: {}", r.message); logHeaders(r.headers); @@ -687,18 +923,12 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { protected SSLContext createSSLContext(Parameters p) { try (FileInputStream in = new FileInputStream(p.keyStoreFileName)) { - System.setProperty("jsse.enableSNIExtension", "false"); - System.setProperty("javax.net.ssl.trustStore", p.trustStoreFileName); - System.setProperty("javax.net.ssl.trustStorePassword", p.trustStorePassword); - HttpsURLConnection.setDefaultHostnameVerifier((string, ssls) -> true); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance("PKCS12"); char[] pwd = p.keyStorePassword.toCharArray(); ks.load(in, pwd); kmf.init(ks, pwd); - SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(kmf.getKeyManagers(), null, null); return ctx; @@ -730,6 +960,16 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { byte[] data = Files.readAllBytes(Paths.get(p.fileName)); r = sendHttpData(data, p); + + for (int i = 0; i < 10 && r.code == 301; i++) { + String newUrl = r.headers2.get("Location").get(0); + + log.info("Got response code 301. Sending same request to URL: " + newUrl); + + p.url = newUrl; + r = sendHttpData(data, p); + } + setResponseStatus(ctx, p.responsePrefix, r); } catch (SvcLogicException | IOException e) { @@ -804,12 +1044,27 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } } - protected HttpResponse sendHttpData(byte[] data, FileParam p) throws SvcLogicException { + protected HttpResponse sendHttpData(byte[] data, FileParam p) throws IOException { + URL url = new URL(p.url); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); - Client client = ClientBuilder.newBuilder().build(); - client.property(ClientProperties.CONNECT_TIMEOUT, 5000); - client.property(ClientProperties.FOLLOW_REDIRECTS, true); - WebTarget webTarget = addAuthType(client, p).target(p.url); + log.info("Connection: " + con.getClass().getName()); + + con.setRequestMethod(p.httpMethod.toString()); + con.setRequestProperty("Content-Type", "application/octet-stream"); + con.setRequestProperty("Accept", "*/*"); + con.setRequestProperty("Expect", "100-continue"); + con.setFixedLengthStreamingMode(data.length); + con.setInstanceFollowRedirects(false); + + if (p.user != null && p.password != null) { + String authString = p.user + ":" + p.password; + String authStringEnc = Base64.getEncoder().encodeToString(authString.getBytes()); + con.setRequestProperty("Authorization", "Basic " + authStringEnc); + } + + con.setDoInput(true); + con.setDoOutput(true); log.info("Sending file"); long t1 = System.currentTimeMillis(); @@ -818,71 +1073,46 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { r.code = 200; if (!p.skipSending) { - String tt = "application/octet-stream"; - Invocation.Builder invocationBuilder = webTarget.request(tt).accept(tt); + HttpURLConnectionMetricUtil util = new HttpURLConnectionMetricUtil(); + util.logBefore(con, ONAPComponents.DMAAP); - Response response; + con.connect(); + boolean continue100failed = false; try { - if (p.httpMethod == HttpMethod.POST) { - response = invocationBuilder.post(Entity.entity(data, tt)); - } else if (p.httpMethod == HttpMethod.PUT) { - response = invocationBuilder.put(Entity.entity(data, tt)); - } else { - throw new SvcLogicException("Http operation" + p.httpMethod + "not supported"); - } - } catch (ProcessingException e) { - throw new SvcLogicException(requestPostingException + - e.getLocalizedMessage(), e); - } - - r.code = response.getStatus(); - r.headers = response.getStringHeaders(); - EntityTag etag = response.getEntityTag(); - if (etag != null) { - r.message = etag.getValue(); - } - if (response.hasEntity() && r.code != 204) { - r.body = response.readEntity(String.class); + OutputStream os = con.getOutputStream(); + os.write(data); + os.flush(); + os.close(); + } catch (ProtocolException e) { + continue100failed = true; } - if (r.code == 301) { - String newUrl = response.getStringHeaders().getFirst("Location"); - - log.info("Got response code 301. Sending same request to URL: {}", newUrl); + r.code = con.getResponseCode(); + r.headers2 = con.getHeaderFields(); - webTarget = client.target(newUrl); - invocationBuilder = webTarget.request(tt).accept(tt); - - try { - if (p.httpMethod == HttpMethod.POST) { - response = invocationBuilder.post(Entity.entity(data, tt)); - } else if (p.httpMethod == HttpMethod.PUT) { - response = invocationBuilder.put(Entity.entity(data, tt)); - } else { - throw new SvcLogicException("Http operation" + p.httpMethod + "not supported"); - } - } catch (ProcessingException e) { - throw new SvcLogicException(requestPostingException + - e.getLocalizedMessage(), e); + if (r.code != 204 && !continue100failed) { + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); } + in.close(); - r.code = response.getStatus(); - etag = response.getEntityTag(); - if (etag != null) { - r.message = etag.getValue(); - } - if (response.hasEntity() && r.code != 204) { - r.body = response.readEntity(String.class); - } + r.body = response.toString(); } + + util.logAfter(con); + + con.disconnect(); } long t2 = System.currentTimeMillis(); - log.info(responseReceivedMessage, (t2 - t1)); - log.info(responseHttpCodeMessage, r.code); + log.info("Response received. Time: {}", t2 - t1); + log.info("HTTP response code: {}", r.code); log.info("HTTP response message: {}", r.message); - logHeaders(r.headers); + logHeaders(r.headers2); log.info("HTTP response: {}", r.body); return r; @@ -930,6 +1160,26 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } } + private void logHeaders(Map> mm) { + if (mm == null || mm.isEmpty()) { + return; + } + + List ll = new ArrayList<>(); + for (String s : mm.keySet()) { + if (s != null) { + ll.add(s); + } + } + Collections.sort(ll); + + for (String name : ll) { + List v = mm.get(name); + log.info("--- {}:{}", name, String.valueOf(mm.get(name))); + log.info("--- " + name + ": " + (v.size() == 1 ? v.get(0) : v)); + } + } + protected HttpResponse postOnUeb(String request, UebParam p) throws SvcLogicException { String[] urls = uebServers.split(" "); for (int i = 0; i < urls.length; i++) { @@ -940,7 +1190,7 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } Client client = ClientBuilder.newBuilder().build(); - client.property(ClientProperties.CONNECT_TIMEOUT, 5000); + setClientTimeouts(client); WebTarget webTarget = client.target(urls[0]); log.info("UEB URL: {}", urls[0]); @@ -961,8 +1211,7 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { try { response = invocationBuilder.post(Entity.entity(request, tt1)); } catch (ProcessingException e) { - throw new SvcLogicException(requestPostingException + - e.getLocalizedMessage(), e); + throw new SvcLogicException(requestPostingException + e.getLocalizedMessage(), e); } r.code = response.getStatus(); r.headers = response.getStringHeaders(); @@ -972,7 +1221,7 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { } long t2 = System.currentTimeMillis(); - log.info(responseReceivedMessage, (t2 - t1)); + log.info(responseReceivedMessage, t2 - t1); log.info(responseHttpCodeMessage, r.code); logHeaders(r.headers); log.info("HTTP response:\n {}", r.body); @@ -988,6 +1237,48 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { this.defaultUebTemplateFileName = defaultUebTemplateFileName; } + protected void setClientTimeouts(Client client) { + client.property(ClientProperties.CONNECT_TIMEOUT, httpConnectTimeout); + client.property(ClientProperties.READ_TIMEOUT, httpReadTimeout); + } + + protected Integer readOptionalInteger(String propertyName, Integer defaultValue) { + String stringValue = System.getProperty(propertyName); + if (stringValue != null && stringValue.length() > 0) { + try { + return Integer.valueOf(stringValue); + } catch (NumberFormatException e) { + log.warn("property " + propertyName + " had the value " + stringValue + " that could not be converted to an Integer, default " + defaultValue + " will be used instead", e); + } + } + return defaultValue; + } + + protected static String[] getMultipleUrls(String restapiUrl) { + List urls = new ArrayList<>(); + int start = 0; + for (int i = 0; i < restapiUrl.length(); i++) { + if (restapiUrl.charAt(i) == ',') { + if (i + 9 < restapiUrl.length()) { + String part = restapiUrl.substring(i + 1, i + 9); + if (part.equals("https://") || part.startsWith("http://")) { + urls.add(restapiUrl.substring(start, i)); + start = i + 1; + } + } + } else if (i == restapiUrl.length() - 1) { + urls.add(restapiUrl.substring(start, i + 1)); + } + } + String[] arr = new String[urls.size()]; + return urls.toArray(arr); + } + + protected static boolean containsMultipleUrls(String restapiUrl) { + Matcher m = retryPattern.matcher(restapiUrl); + return m.matches(); + } + private static class FileParam { public String fileName; @@ -1012,4 +1303,4 @@ public class RestapiCallNode implements SvcLogicJavaPlugin { public String responsePrefix; public boolean skipSending; } -} \ No newline at end of file +}