diff options
Diffstat (limited to 'plugins/restapi-call-node/provider/src/main/java')
12 files changed, 2453 insertions, 0 deletions
diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/AuthType.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/AuthType.java new file mode 100644 index 000000000..851dc9cc8 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/AuthType.java @@ -0,0 +1,19 @@ +package org.onap.ccsdk.sli.plugins.restapicall; + +public enum AuthType { + NONE, BASIC, DIGEST, OAUTH, Unspecified; + + public static AuthType fromString(String s) { + if ("basic".equalsIgnoreCase(s)) + return BASIC; + if ("digest".equalsIgnoreCase(s)) + return DIGEST; + if ("oauth".equalsIgnoreCase(s)) + return OAUTH; + if ("none".equalsIgnoreCase(s)) + return NONE; + if ("unspecified".equalsIgnoreCase(s)) + return Unspecified; + throw new IllegalArgumentException("Invalid value for format: " + s); + } +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/Format.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/Format.java new file mode 100644 index 000000000..1578ee320 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/Format.java @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +public enum Format { + JSON, XML, NONE; + + public static Format fromString(String s) { + if ("json".equalsIgnoreCase(s)) + return JSON; + if ("xml".equalsIgnoreCase(s)) + return XML; + if ("none".equalsIgnoreCase(s)) + return NONE; + throw new IllegalArgumentException("Invalid value for format: " + s); + } +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/HttpMethod.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/HttpMethod.java new file mode 100644 index 000000000..b2f618a1c --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/HttpMethod.java @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * Modifications Copyright © 2018 IBM. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +public enum HttpMethod { + GET, POST, PUT, DELETE, PATCH; + + public static HttpMethod fromString(String s) { + if (s == null) + return null; + if (("get").equalsIgnoreCase(s)) + return GET; + if (("post").equalsIgnoreCase(s)) + return POST; + if (("put").equalsIgnoreCase(s)) + return PUT; + if (("delete").equalsIgnoreCase(s)) + return DELETE; + if (("patch").equalsIgnoreCase(s)) + return PATCH; + throw new IllegalArgumentException("Invalid value for HTTP Method: " + s); + } +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/HttpResponse.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/HttpResponse.java new file mode 100644 index 000000000..574089528 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/HttpResponse.java @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MultivaluedMap; + +public class HttpResponse { + public int code; + public String message; + public String body; + public MultivaluedMap<String, String> headers; + public Map<String, List<String>> headers2; +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/JsonParser.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/JsonParser.java new file mode 100644 index 000000000..60d43df7a --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/JsonParser.java @@ -0,0 +1,153 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.onap.ccsdk.sli.core.sli.SvcLogicException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class JsonParser { + + private static final Logger log = LoggerFactory.getLogger(JsonParser.class); + + private JsonParser() { + // Preventing instantiation of the same. + } + + + private static void handleJsonArray(String key, Map<String, Object> jArrayMap, JSONArray jsonArr) throws JSONException { + JSONObject jsonObj; + JSONArray subJsonArr; + boolean stripKey = false; + + for (int i = 0, length = jsonArr.length(); i < length; i++) { + if (stripKey) + key = key.substring(0, key.length()-3); + + subJsonArr = jsonArr.optJSONArray(i); + if (subJsonArr != null) { + key = StringUtils.trimToEmpty(key) + "[" + i + "]"; + jArrayMap.putIfAbsent(key + "_length", String.valueOf(subJsonArr.length())); + handleJsonArray(key, jArrayMap, subJsonArr); + stripKey = true; + continue; + } + + jsonObj = jsonArr.optJSONObject(i); + if (jsonObj != null) { + Iterator<String> ii = jsonObj.keys(); + while (ii.hasNext()) { + String nodeKey = ii.next(); + String key1 = "[" + i + "]." + nodeKey; + String[] subKey = key1.split(":"); + if (subKey.length == 2) { + jArrayMap.putIfAbsent(subKey[1], jsonObj.get(nodeKey)); + } else { + jArrayMap.putIfAbsent(key1, jsonObj.get(nodeKey)); + } + } + } + else { + jArrayMap.putIfAbsent(StringUtils.trimToEmpty(key), jsonArr); + break; + } + } + } + + @SuppressWarnings("unchecked") + public static Map<String, String> convertToProperties(String s) + throws SvcLogicException { + + checkNotNull(s, "Input should not be null."); + + try { + Map<String, Object> wm = new HashMap<>(); + JSONObject json; + JSONArray jsonArr; + //support top level list in json response + if (s.startsWith("[")) { + jsonArr = new JSONArray(s); + wm.put("_length", String.valueOf(jsonArr.length())); + handleJsonArray(null, wm, jsonArr); + } else { + json = new JSONObject(s); + Iterator<String> ii = json.keys(); + while (ii.hasNext()) { + String key1 = ii.next(); + String[] subKey = key1.split(":"); + if (subKey.length == 2) { + wm.put(subKey[1], json.get(key1)); + } else { + wm.put(key1, json.get(key1)); + } + } + } + + Map<String, String> mm = new HashMap<>(); + while (!wm.isEmpty()) { + for (String key : new ArrayList<>(wm.keySet())) { + Object o = wm.get(key); + wm.remove(key); + + if (o instanceof Boolean || o instanceof Number || o instanceof String) { + mm.put(key, o.toString()); + log.info("Added property: {} : {}", key, o.toString()); + } else if (o instanceof JSONObject) { + JSONObject jo = (JSONObject) o; + Iterator<String> i = jo.keys(); + while (i.hasNext()) { + String key1 = i.next(); + String[] subKey = key1.split(":"); + if (subKey.length == 2) { + wm.put(key + "." + subKey[1], jo.get(key1)); + } else { + wm.put(key + "." + key1, jo.get(key1)); + } + } + } else if (o instanceof JSONArray) { + JSONArray ja = (JSONArray) o; + mm.put(key + "_length", String.valueOf(ja.length())); + log.info("Added property: {}_length: {}", key, ja.length()); + + for (int i = 0; i < ja.length(); i++) { + wm.put(key + '[' + i + ']', ja.get(i)); + } + } + } + } + return mm; + } catch (JSONException e) { + throw new SvcLogicException("Unable to convert JSON to properties" + e.getLocalizedMessage(), e); + } + } + +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/Parameters.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/Parameters.java new file mode 100755 index 000000000..9b542af91 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/Parameters.java @@ -0,0 +1,56 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +import java.util.Set; + +public class Parameters { + public String templateFileName; + public String restapiUrl; + public String restapiUrlSuffix; + public String restapiUser; + public String restapiPassword; + public Format format; + public String contentType; + public HttpMethod httpMethod; + public String responsePrefix; + public Set<String> listNameList; + public boolean skipSending; + public boolean convertResponse; + public String keyStoreFileName; + public String keyStorePassword; + public boolean ssl; + public String customHttpHeaders; + public String partner; + public Boolean dumpHeaders; + public String requestBody; + public String oAuthConsumerKey; + public String oAuthConsumerSecret; + public String oAuthSignatureMethod; + public String oAuthVersion; + public AuthType authtype; + public Boolean returnRequestPayload; + public String accept; + public boolean multipartFormData; + public String multipartFile; + public String targetEntity; +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/PartnerDetails.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/PartnerDetails.java new file mode 100755 index 000000000..1cd4b99bb --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/PartnerDetails.java @@ -0,0 +1,14 @@ +package org.onap.ccsdk.sli.plugins.restapicall;
+
+public class PartnerDetails {
+ protected String username;
+ protected String password;
+ protected String url;
+
+ public PartnerDetails(String username, String password, String url) {
+ this.username = username;
+ this.password = password;
+ this.url = url;
+ }
+
+}
diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java new file mode 100755 index 000000000..3d7042493 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RestapiCallNode.java @@ -0,0 +1,1306 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * Modifications Copyright © 2018 IBM. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +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; +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +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 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); + 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 "; + 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<String, PartnerDetails> partnerStore; + private static final Pattern retryPattern = Pattern.compile(".*,(http|https):.*"); + + public RestapiCallNode() { + String configDir = System.getProperty(PROPERTIES_DIR_KEY, DEFAULT_PROPERTIES_DIR); + 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("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); + 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.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<String> 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 + * @return parameters filed instance + * @throws SvcLogicException when svc logic exception occurs + */ + public static Parameters getParameters(Map<String, String> 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, 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.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.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.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; + } + + /** + * Validates the given URL in the parameters. + * + * @param restapiUrl rest api URL + * @throws SvcLogicException when URL validation fails + */ + 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); + } + } + } + + /** + * Returns the list of list name. + * + * @param paramMap parameters map + * @return list of list name + */ + private static Set<String> getListNameList(Map<String, String> paramMap) { + Set<String> ll = new HashSet<>(); + for (Map.Entry<String, String> entry : paramMap.entrySet()) { + if (entry.getKey().startsWith("listName")) { + ll.add(entry.getValue()); + } + } + return ll; + } + + /** + * 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 required if value required + * @param def default value + * @return value of the property + * @throws SvcLogicException if required parameter value is empty + */ + public static String parseParam(Map<String, String> paramMap, String name, boolean required, String def) + throws SvcLogicException { + String s = paramMap.get(name); + + if (s == null || s.trim().length() == 0) { + if (!required) { + return def; + } + throw new SvcLogicException("Parameter " + name + " is required in RestapiCallNode"); + } + + s = s.trim(); + StringBuilder value = new StringBuilder(); + int i = 0; + int i1 = s.indexOf('%'); + while (i1 >= 0) { + int i2 = s.indexOf('%', i1 + 1); + if (i2 < 0) { + break; + } + + String varName = s.substring(i1 + 1, i2); + String varValue = System.getenv(varName); + if (varValue == null) { + varValue = "%" + varName + "%"; + } + + value.append(s.substring(i, i1)); + value.append(varValue); + + i = i2 + 1; + i1 = s.indexOf('%', i); + } + value.append(s.substring(i)); + + log.info("Parameter {}: [{}]", name, maskPassword(name, value)); + + return value.toString(); + } + + 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. + * + * @param paramMap HashMap<String,String> of parameters passed by the DG to this function + * <table border="1"> + * <thead> + * <th>parameter</th> + * <th>Mandatory/Optional</th> + * <th>description</th> + * <th>example values</th></thead> <tbody> + * <tr> + * <td>templateFileName</td> + * <td>Optional</td> + * <td>full path to template file that can be used to build a request</td> + * <td>/sdncopt/bvc/restapi/templates/vnf_service-configuration-operation_minimal.json</td> + * </tr> + * <tr> + * <td>restapiUrl</td> + * <td>Mandatory</td> + * <td>url to send the request to</td> + * <td>https://sdncodl:8543/restconf/operations/L3VNF-API:create-update-vnf-request</td> + * </tr> + * <tr> + * <td>restapiUser</td> + * <td>Optional</td> + * <td>user name to use for http basic authentication</td> + * <td>sdnc_ws</td> + * </tr> + * <tr> + * <td>restapiPassword</td> + * <td>Optional</td> + * <td>unencrypted password to use for http basic authentication</td> + * <td>plain_password</td> + * </tr> + * <tr> + * <td>oAuthConsumerKey</td> + * <td>Optional</td> + * <td>Consumer key to use for http oAuth authentication</td> + * <td>plain_key</td> + * </tr> + * <tr> + * <td>oAuthConsumerSecret</td> + * <td>Optional</td> + * <td>Consumer secret to use for http oAuth authentication</td> + * <td>plain_secret</td> + * </tr> + * <tr> + * <td>oAuthSignatureMethod</td> + * <td>Optional</td> + * <td>Consumer method to use for http oAuth authentication</td> + * <td>method</td> + * </tr> + * <tr> + * <td>oAuthVersion</td> + * <td>Optional</td> + * <td>Version http oAuth authentication</td> + * <td>version</td> + * </tr> + * <tr> + * <td>contentType</td> + * <td>Optional</td> + * <td>http content type to set in the http header</td> + * <td>usually application/json or application/xml</td> + * </tr> + * <tr> + * <td>format</td> + * <td>Optional</td> + * <td>should match request body format</td> + * <td>json or xml</td> + * </tr> + * <tr> + * <td>httpMethod</td> + * <td>Optional</td> + * <td>http method to use when sending the request</td> + * <td>get post put delete patch</td> + * </tr> + * <tr> + * <td>responsePrefix</td> + * <td>Optional</td> + * <td>location the response will be written to in context memory</td> + * <td>tmp.restapi.result</td> + * </tr> + * <tr> + * <td>listName[i]</td> + * <td>Optional</td> + * <td>Used for processing XML responses with repeating + * elements.</td>vpn-information.vrf-details + * <td></td> + * </tr> + * <tr> + * <td>skipSending</td> + * <td>Optional</td> + * <td></td> + * <td>true or false</td> + * </tr> + * <tr> + * <td>convertResponse</td> + * <td>Optional</td> + * <td>whether the response should be converted</td> + * <td>true or false</td> + * </tr> + * <tr> + * <td>customHttpHeaders</td> + * <td>Optional</td> + * <td>a list additional http headers to be passed in, follow the format in the example</td> + * <td>X-CSI-MessageId=messageId,headerFieldName=headerFieldValue</td> + * </tr> + * <tr> + * <td>dumpHeaders</td> + * <td>Optional</td> + * <td>when true writes http header content to context memory</td> + * <td>true or false</td> + * </tr> + * <tr> + * <td>partner</td> + * <td>Optional</td> + * <td>used to retrieve username, password and url if partner store exists</td> + * <td>aaf</td> + * </tr> + * <tr> + * <td>returnRequestPayload</td> + * <td>Optional</td> + * <td>used to return payload built in the request</td> + * <td>true or false</td> + * </tr> + * </tbody> + * </table> + * @param ctx Reference to context memory + * @throws SvcLogicException + * @since 11.0.2 + * @see String#split(String, int) + */ + public void sendRequest(Map<String, String> paramMap, SvcLogicContext ctx) throws SvcLogicException { + sendRequest(paramMap, ctx, null); + } + + protected void sendRequest(Map<String, String> paramMap, SvcLogicContext ctx, RetryPolicy retryPolicy) + throws SvcLogicException { + + HttpResponse r = new HttpResponse(); + try { + handlePartner(paramMap); + Parameters p = getParameters(paramMap, new Parameters()); + 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 + '.' : ""; + + String req = null; + if (p.templateFileName != null) { + String reqTemplate = readFile(p.templateFileName); + req = buildXmlJsonRequest(ctx, reqTemplate, p.format); + } else if (p.requestBody != null) { + req = p.requestBody; + } + r = sendHttpRequest(req, p); + setResponseStatus(ctx, p.responsePrefix, r); + + if (p.dumpHeaders && r.headers != null) { + for (Entry<String, List<String>> a : r.headers.entrySet()) { + ctx.setAttribute(pp + "header." + a.getKey(), StringUtils.join(a.getValue(), ",")); + } + } + + if (p.returnRequestPayload && req != null) { + ctx.setAttribute(pp + "httpRequest", req); + } + + if (r.body != null && r.body.trim().length() > 0) { + ctx.setAttribute(pp + "httpResponse", r.body); + + if (p.convertResponse) { + Map<String, String> mm = null; + if (p.format == Format.XML) { + mm = XmlParser.convertToProperties(r.body, p.listNameList); + } else if (p.format == Format.JSON) { + mm = JsonParser.convertToProperties(r.body); + } + + if (mm != null) { + for (Map.Entry<String, String> entry : mm.entrySet()) { + ctx.setAttribute(pp + entry.getKey(), entry.getValue()); + } + } + } + } + } catch (SvcLogicException e) { + boolean shouldRetry = false; + if (e.getCause().getCause() instanceof SocketException) { + shouldRetry = true; + } + + log.error("Error sending the request: " + e.getMessage(), e); + String prefix = parseParam(paramMap, responsePrefix, false, null); + if (retryPolicy == null || !shouldRetry) { + setFailureResponseStatus(ctx, prefix, e.getMessage(), r); + } else { + log.debug(retryPolicy.getRetryMessage()); + try { + // 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, won't attempt to retry. Calling setFailureResponseStatus."); + setFailureResponseStatus(ctx, prefix, e.getMessage(), r); + } + } catch (Exception ex) { + String retryErrorMessage = "Retry attempt " + retryPolicy.getRetryCount() + + "has failed with error message " + ex.getMessage(); + setFailureResponseStatus(ctx, prefix, retryErrorMessage, r); + } + } + } + + if (r != null && r.code >= 300) { + throw new SvcLogicException(String.valueOf(r.code) + ": " + r.message); + } + } + + protected void handlePartner(Map<String, String> 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; + + template = expandRepeats(ctx, template, 1); + + Map<String, String> mm = new HashMap<>(); + for (String s : ctx.getAttributeKeySet()) { + mm.put(s, ctx.getAttribute(s)); + } + + StringBuilder ss = new StringBuilder(); + int i = 0; + while (i < template.length()) { + int i1 = template.indexOf("${", i); + if (i1 < 0) { + ss.append(template.substring(i)); + break; + } + + int i2 = template.indexOf('}', i1 + 2); + if (i2 < 0) { + throw new SvcLogicException("Template error: Matching } not found"); + } + + String var1 = template.substring(i1 + 2, i2); + String value1 = format == Format.XML ? XmlJsonUtil.getXml(mm, var1) : XmlJsonUtil.getJson(mm, var1); + if (value1 == null || value1.trim().length() == 0) { + // delete the whole element (line) + int i3 = template.lastIndexOf('\n', i1); + if (i3 < 0) { + i3 = 0; + } + int i4 = template.indexOf('\n', i1); + if (i4 < 0) { + i4 = template.length(); + } + + if (i < i3) { + ss.append(template.substring(i, i3)); + } + i = i4; + } else { + ss.append(template.substring(i, i1)).append(value1); + i = i2 + 1; + } + } + + 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); + + return req; + } + + protected String expandRepeats(SvcLogicContext ctx, String template, int level) throws SvcLogicException { + StringBuilder newTemplate = new StringBuilder(); + int k = 0; + while (k < template.length()) { + int i1 = template.indexOf("${repeat:", k); + if (i1 < 0) { + newTemplate.append(template.substring(k)); + break; + } + + int i2 = template.indexOf(':', i1 + 9); + if (i2 < 0) { + throw new SvcLogicException( + "Template error: Context variable name followed by : is required after repeat"); + } + + // Find the closing }, store in i3 + int nn = 1; + int i3 = -1; + int i = i2; + while (nn > 0 && i < template.length()) { + i3 = template.indexOf('}', i); + if (i3 < 0) { + throw new SvcLogicException("Template error: Matching } not found"); + } + int i32 = template.indexOf('{', i); + if (i32 >= 0 && i32 < i3) { + nn++; + i = i32 + 1; + } else { + nn--; + i = i3 + 1; + } + } + + String var1 = template.substring(i1 + 9, i2); + String value1 = ctx.getAttribute(var1); + log.info(" {}:{}", var1, value1); + int n = 0; + try { + n = Integer.parseInt(value1); + } catch (NumberFormatException e) { + log.info("value1 not set or not a number, n will remain set at zero"); + } + + newTemplate.append(template.substring(k, i1)); + + String rpt = template.substring(i2 + 1, i3); + + for (int ii = 0; ii < n; ii++) { + String ss = rpt.replaceAll("\\[\\$\\{" + level + "\\}\\]", "[" + ii + "]"); + if (ii == n - 1 && ss.trim().endsWith(",")) { + int i4 = ss.lastIndexOf(','); + if (i4 > 0) { + ss = ss.substring(0, i4) + ss.substring(i4 + 1); + } + } + newTemplate.append(ss); + } + + k = i3 + 1; + } + + if (k == 0) { + return newTemplate.toString(); + } + + return expandRepeats(ctx, newTemplate.toString(), level + 1); + } + + protected String readFile(String fileName) throws SvcLogicException { + try { + byte[] encoded = Files.readAllBytes(Paths.get(fileName)); + return new String(encoded, "UTF-8"); + } catch (IOException | SecurityException e) { + throw new SvcLogicException("Unable to read file " + fileName + e.getLocalizedMessage(), e); + } + } + + protected Client addAuthType(Client c, FileParam fp) throws SvcLogicException { + Parameters p = new Parameters(); + p.restapiUser = fp.user; + p.restapiPassword = fp.password; + p.oAuthConsumerKey = fp.oAuthConsumerKey; + p.oAuthVersion = fp.oAuthVersion; + p.oAuthConsumerSecret = fp.oAuthConsumerSecret; + p.oAuthSignatureMethod = fp.oAuthSignatureMethod; + p.authtype = fp.authtype; + return addAuthType(c, p); + } + + public Client addAuthType(Client client, Parameters p) throws SvcLogicException { + 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(); + client.register(oAuth1Feature); + + } + } else { + if (p.authtype == AuthType.DIGEST) { + if (p.restapiUser != null && p.restapiPassword != null) { + 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()); + } + } 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()); + } + } else if (p.authtype == AuthType.OAUTH) { + 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 { + throw new SvcLogicException( + "oAUTH authentication type selected but all oAuthConsumerKey, oAuthConsumerSecret " + + "and oAuthSignatureMethod parameters doesn't exist", + new Throwable()); + } + } + } + return client; + } + + /** + * Receives the http response for the http request sent. + * + * @param request request msg + * @param p parameters + * @return HTTP response + * @throws SvcLogicException when sending http request fails + */ + 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(); + } else { + client = ClientBuilder.newBuilder().hostnameVerifier((s, sslSession) -> true).build(); + } + + 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); + + 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"; + } + + String contentType = p.contentType; + if (contentType == null) { + contentType = accept + ";charset=UTF-8"; + } + + if (!p.skipSending && !p.multipartFormData) { + + Invocation.Builder invocationBuilder = webTarget.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())); + } + } + + 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(multiPart, multiPart.getMediaType())); + } 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); + } + + } + + long t2 = System.currentTimeMillis(); + log.info(responseReceivedMessage, t2 - t1); + log.info(responseHttpCodeMessage, r.code); + log.info("HTTP response message: {}", r.message); + logHeaders(r.headers); + log.info("HTTP response: {}", r.body); + + return r; + } + + protected SSLContext createSSLContext(Parameters p) { + try (FileInputStream in = new FileInputStream(p.keyStoreFileName)) { + 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; + } catch (Exception e) { + log.error("Error creating SSLContext: {}", e.getMessage(), e); + } + return null; + } + + protected void setFailureResponseStatus(SvcLogicContext ctx, String prefix, String errorMessage, + HttpResponse resp) { + resp.code = 500; + resp.message = errorMessage; + String pp = prefix != null ? prefix + '.' : ""; + ctx.setAttribute(pp + "response-code", String.valueOf(resp.code)); + ctx.setAttribute(pp + "response-message", resp.message); + } + + protected void setResponseStatus(SvcLogicContext ctx, String prefix, HttpResponse r) { + String pp = prefix != null ? prefix + '.' : ""; + ctx.setAttribute(pp + "response-code", String.valueOf(r.code)); + ctx.setAttribute(pp + "response-message", r.message); + } + + public void sendFile(Map<String, String> paramMap, SvcLogicContext ctx) throws SvcLogicException { + HttpResponse r = null; + try { + FileParam p = getFileParameters(paramMap); + 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) { + log.error("Error sending the request: {}", e.getMessage(), e); + + r = new HttpResponse(); + r.code = 500; + r.message = e.getMessage(); + String prefix = parseParam(paramMap, responsePrefix, false, null); + setResponseStatus(ctx, prefix, r); + } + + if (r != null && r.code >= 300) { + throw new SvcLogicException(String.valueOf(r.code) + ": " + r.message); + } + } + + private FileParam getFileParameters(Map<String, String> paramMap) throws SvcLogicException { + FileParam p = new FileParam(); + p.fileName = parseParam(paramMap, "fileName", true, null); + p.url = parseParam(paramMap, "url", true, null); + p.user = parseParam(paramMap, "user", false, null); + p.password = parseParam(paramMap, "password", false, null); + p.httpMethod = HttpMethod.fromString(parseParam(paramMap, "httpMethod", false, "post")); + p.responsePrefix = parseParam(paramMap, responsePrefix, false, null); + String skipSendingStr = paramMap.get(skipSendingMessage); + p.skipSending = "true".equalsIgnoreCase(skipSendingStr); + p.oAuthConsumerKey = parseParam(paramMap, "oAuthConsumerKey", false, null); + p.oAuthVersion = parseParam(paramMap, "oAuthVersion", false, null); + p.oAuthConsumerSecret = parseParam(paramMap, "oAuthConsumerSecret", false, null); + p.oAuthSignatureMethod = parseParam(paramMap, "oAuthSignatureMethod", false, null); + p.authtype = fromString(parseParam(paramMap, "authType", false, "unspecified")); + return p; + } + + public void postMessageOnUeb(Map<String, String> paramMap, SvcLogicContext ctx) throws SvcLogicException { + HttpResponse r; + try { + UebParam p = getUebParameters(paramMap); + + String pp = p.responsePrefix != null ? p.responsePrefix + '.' : ""; + + String req; + + if (p.templateFileName == null) { + log.info("No template file name specified. Using default UEB template: {}", defaultUebTemplateFileName); + p.templateFileName = defaultUebTemplateFileName; + } + + String reqTemplate = readFile(p.templateFileName); + reqTemplate = reqTemplate.replaceAll("rootVarName", p.rootVarName); + req = buildXmlJsonRequest(ctx, reqTemplate, Format.JSON); + + r = postOnUeb(req, p); + setResponseStatus(ctx, p.responsePrefix, r); + if (r.body != null) { + ctx.setAttribute(pp + "httpResponse", r.body); + } + + } catch (SvcLogicException e) { + log.error("Error sending the request: {}", e.getMessage(), e); + + r = new HttpResponse(); + r.code = 500; + r.message = e.getMessage(); + String prefix = parseParam(paramMap, responsePrefix, false, null); + setResponseStatus(ctx, prefix, r); + } + + if (r.code >= 300) { + throw new SvcLogicException(String.valueOf(r.code) + ": " + r.message); + } + } + + protected HttpResponse sendHttpData(byte[] data, FileParam p) throws IOException { + URL url = new URL(p.url); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + + 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(); + + HttpResponse r = new HttpResponse(); + r.code = 200; + + if (!p.skipSending) { + HttpURLConnectionMetricUtil util = new HttpURLConnectionMetricUtil(); + util.logBefore(con, ONAPComponents.DMAAP); + + con.connect(); + + boolean continue100failed = false; + try { + OutputStream os = con.getOutputStream(); + os.write(data); + os.flush(); + os.close(); + } catch (ProtocolException e) { + continue100failed = true; + } + + r.code = con.getResponseCode(); + r.headers2 = con.getHeaderFields(); + + 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.body = response.toString(); + } + + util.logAfter(con); + + con.disconnect(); + } + + long t2 = System.currentTimeMillis(); + log.info("Response received. Time: {}", t2 - t1); + log.info("HTTP response code: {}", r.code); + log.info("HTTP response message: {}", r.message); + logHeaders(r.headers2); + log.info("HTTP response: {}", r.body); + + return r; + } + + private UebParam getUebParameters(Map<String, String> paramMap) throws SvcLogicException { + UebParam p = new UebParam(); + p.topic = parseParam(paramMap, "topic", true, null); + p.templateFileName = parseParam(paramMap, "templateFileName", false, null); + p.rootVarName = parseParam(paramMap, "rootVarName", false, null); + p.responsePrefix = parseParam(paramMap, responsePrefix, false, null); + String skipSendingStr = paramMap.get(skipSendingMessage); + p.skipSending = "true".equalsIgnoreCase(skipSendingStr); + return p; + } + + protected void logProperties(Map<String, Object> mm) { + List<String> ll = new ArrayList<>(); + for (Object o : mm.keySet()) { + ll.add((String) o); + } + Collections.sort(ll); + + log.info("Properties:"); + for (String name : ll) { + log.info("--- {}:{}", name, String.valueOf(mm.get(name))); + } + } + + protected void logHeaders(MultivaluedMap<String, String> mm) { + log.info("HTTP response headers:"); + + if (mm == null) { + return; + } + + List<String> ll = new ArrayList<>(); + for (Object o : mm.keySet()) { + ll.add((String) o); + } + Collections.sort(ll); + + for (String name : ll) { + log.info("--- {}:{}", name, String.valueOf(mm.get(name))); + } + } + + private void logHeaders(Map<String, List<String>> mm) { + if (mm == null || mm.isEmpty()) { + return; + } + + List<String> ll = new ArrayList<>(); + for (String s : mm.keySet()) { + if (s != null) { + ll.add(s); + } + } + Collections.sort(ll); + + for (String name : ll) { + List<String> 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++) { + if (!urls[i].endsWith("/")) { + urls[i] += "/"; + } + urls[i] += "events/" + p.topic; + } + + Client client = ClientBuilder.newBuilder().build(); + setClientTimeouts(client); + WebTarget webTarget = client.target(urls[0]); + + log.info("UEB URL: {}", urls[0]); + log.info("Sending request:"); + log.info(request); + long t1 = System.currentTimeMillis(); + + HttpResponse r = new HttpResponse(); + r.code = 200; + + if (!p.skipSending) { + String tt = "application/json"; + String tt1 = tt + ";charset=UTF-8"; + + Response response; + Invocation.Builder invocationBuilder = webTarget.request(tt1).accept(tt); + + try { + response = invocationBuilder.post(Entity.entity(request, tt1)); + } catch (ProcessingException e) { + throw new SvcLogicException(requestPostingException + e.getLocalizedMessage(), e); + } + r.code = response.getStatus(); + r.headers = response.getStringHeaders(); + if (response.hasEntity()) { + r.body = response.readEntity(String.class); + } + } + + long t2 = System.currentTimeMillis(); + log.info(responseReceivedMessage, t2 - t1); + log.info(responseHttpCodeMessage, r.code); + logHeaders(r.headers); + log.info("HTTP response:\n {}", r.body); + + return r; + } + + public void setUebServers(String uebServers) { + this.uebServers = uebServers; + } + + public void setDefaultUebTemplateFileName(String defaultUebTemplateFileName) { + 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<String> 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; + public String url; + public String user; + public String password; + public HttpMethod httpMethod; + public String responsePrefix; + public boolean skipSending; + public String oAuthConsumerKey; + public String oAuthConsumerSecret; + public String oAuthSignatureMethod; + public String oAuthVersion; + public AuthType authtype; + } + + private static class UebParam { + + public String topic; + public String templateFileName; + public String rootVarName; + public String responsePrefix; + public boolean skipSending; + } +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RetryException.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RetryException.java new file mode 100644 index 000000000..03078c652 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RetryException.java @@ -0,0 +1,30 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +public class RetryException extends Exception { + + public RetryException(String message) { + super(message); + } + +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RetryPolicy.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RetryPolicy.java new file mode 100644 index 000000000..8d5143be3 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/RetryPolicy.java @@ -0,0 +1,64 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * Modifications Copyright © 2018 IBM + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +public class RetryPolicy { + private String[] hostnames; + private Integer maximumRetries; + private int position; + private int retryCount; + + public RetryPolicy(String[] hostnames, Integer maximumRetries) { + this.hostnames = hostnames; + this.maximumRetries = maximumRetries; + this.position = 0; + this.retryCount = 0; + + } + + public Integer getMaximumRetries() { + return maximumRetries; + } + + public int getRetryCount() { + return retryCount; + } + + public Boolean shouldRetry() { + return retryCount < maximumRetries + 1; + } + + public String getRetryMessage() { + return retryCount + " retry attempts were made out of " + maximumRetries + " maximum retry attempts."; + } + + public String getNextHostName() throws RetryException { + retryCount++; + position++; + if (position > hostnames.length - 1) { + position = 0; + } + return hostnames[position]; + } + +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/XmlJsonUtil.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/XmlJsonUtil.java new file mode 100644 index 000000000..9870221d7 --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/XmlJsonUtil.java @@ -0,0 +1,518 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class XmlJsonUtil { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(XmlJsonUtil.class); + + private XmlJsonUtil() { + // Preventing instantiation of the same. + } + + public static String getXml(Map<String, String> varmap, String var) { + boolean escape = true; + if (var.startsWith("'")) { + var = var.substring(1); + escape = false; + } + + Object o = createStructure(varmap, var); + return generateXml(o, 0, escape); + } + + public static String getJson(Map<String, String> varmap, String var) { + boolean escape = true; + if (var.startsWith("'")) { + var = var.substring(1); + escape = false; + } + + boolean quotes = true; + if (var.startsWith("\"")) { + var = var.substring(1); + quotes = false; + } + + Object o = createStructure(varmap, var); + return generateJson(o, escape, quotes); + } + + private static Object createStructure(Map<String, String> flatmap, String var) { + if (flatmap.containsKey(var)) { + return flatmap.get(var); + } + + Map<String, Object> mm = new HashMap<>(); + List<Object> ll = new ArrayList<>(); + + for (Map.Entry<String, String> e : flatmap.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + + if (key.endsWith("_length") || key.endsWith("].key")) { + continue; + } + + if (value == null || value.trim().isEmpty()) { + continue; + } + + if (key.startsWith(var + "[")) { + String newKey = key.substring(var.length()); + set(ll, newKey, value); + } else if (var == null || var.isEmpty()) { + set(mm, key, value); + } else if (key.startsWith(var + ".")) { + String newKey = key.substring(var.length() + 1); + set(mm, newKey, value); + } + } + + if (!mm.isEmpty()) { + return mm; + } + if (!ll.isEmpty()) { + return ll; + } + return null; + } + + @SuppressWarnings("unchecked") + public static void set(Object struct, String compositeKey, Object value) { + if (struct == null) { + throw new IllegalArgumentException("Null argument: struct"); + } + + if (compositeKey == null || compositeKey.length() == 0) { + throw new IllegalArgumentException("Null or empty argument: compositeKey"); + } + + if (value == null) { + return; + } + + List<Object> keys = splitCompositeKey(compositeKey); + Object currentValue = struct; + String currentKey = ""; + + for (int i = 0; i < keys.size() - 1; i++) { + Object key = keys.get(i); + + if (key instanceof Integer) { + if (!(currentValue instanceof List)) { + throw new IllegalArgumentException("Cannot resolve: " + compositeKey + ": References list '" + currentKey + "', but '" + currentKey + "' is not a list"); + } + + Integer keyi = (Integer) key; + List<Object> currentValueL = (List<Object>) currentValue; + int size = currentValueL.size(); + + if (keyi >= size) { + for (int k = 0; k < keyi - size + 1; k++) { + currentValueL.add(null); + } + } + + Object newValue = currentValueL.get(keyi); + if (newValue == null) { + Object nextKey = keys.get(i + 1); + if (nextKey instanceof Integer) { + newValue = new ArrayList<>(); + } else { + newValue = new HashMap<>(); + } + currentValueL.set(keyi, newValue); + } + + currentValue = newValue; + currentKey += "[" + key + "]"; + + } else { + if (!(currentValue instanceof Map)) { + throw new IllegalArgumentException("Cannot resolve: " + compositeKey + ": References map '" + currentKey + "', but '" + currentKey + "' is not a map"); + } + + Object newValue = ((Map<String, Object>) currentValue).get(key); + if (newValue == null) { + Object nextKey = keys.get(i + 1); + if (nextKey instanceof Integer) { + newValue = new ArrayList<>(); + } else { + newValue = new HashMap<>(); + } + ((Map<String, Object>) currentValue).put((String) key, newValue); + } + + currentValue = newValue; + currentKey += "." + key; + } + } + + Object key = keys.get(keys.size() - 1); + if (key instanceof Integer) { + if (!(currentValue instanceof List)) { + throw new IllegalArgumentException("Cannot resolve: " + compositeKey + ": References list '" + currentKey + "', but '" + currentKey + "' is not a list"); + } + + Integer keyi = (Integer) key; + List<Object> currentValueL = (List<Object>) currentValue; + int size = currentValueL.size(); + + if (keyi >= size) { + for (int k = 0; k < keyi - size + 1; k++) { + currentValueL.add(null); + } + } + + currentValueL.set(keyi, value); + + } else { + if (!(currentValue instanceof Map)) { + throw new IllegalArgumentException("Cannot resolve: " + compositeKey + ": References map '" + currentKey + "', but '" + currentKey + "' is not a map"); + } + + ((Map<String, Object>) currentValue).put((String) key, value); + } + } + + private static List<Object> splitCompositeKey(String compositeKey) { + if (compositeKey == null) { + return Collections.emptyList(); + } + + String[] ss = compositeKey.split("\\."); + List<Object> ll = new ArrayList<>(); + for (String s : ss) { + if (s.length() == 0) { + continue; + } + + int i1 = s.indexOf('['); + if (i1 < 0) { + ll.add(s); + } else { + if (!s.endsWith("]")) { + throw new IllegalArgumentException("Invalid composite key: " + compositeKey + ": No matching ] found"); + } + + String s1 = s.substring(0, i1); + if (s1.length() > 0) { + ll.add(s1); + } + + String s2 = s.substring(i1 + 1, s.length() - 1); + try { + int n = Integer.parseInt(s2); + if (n < 0) { + throw new IllegalArgumentException("Invalid composite key: " + compositeKey + ": Index must be >= 0: " + n); + } + + ll.add(n); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid composite key: " + compositeKey + ": Index not a number: " + s2); + } + } + } + + return ll; + } + + @SuppressWarnings("unchecked") + private static String generateXml(Object o, int indent, boolean escape) { + if (o == null) { + return null; + } + + if (o instanceof String) { + return escape ? StringEscapeUtils.escapeXml10((String) o) : (String) o; + }; + + if (o instanceof Map) { + StringBuilder ss = new StringBuilder(); + Map<String, Object> mm = (Map<String, Object>) o; + for (Map.Entry<String, Object> entry : mm.entrySet()) { + Object v = entry.getValue(); + String key = entry.getKey(); + if (v instanceof String) { + String s = escape ? StringEscapeUtils.escapeXml10((String) v) : (String) v; + ss.append(pad(indent)).append('<').append(key).append('>'); + ss.append(s); + ss.append("</").append(key).append('>').append('\n'); + } else if (v instanceof Map) { + ss.append(pad(indent)).append('<').append(key).append('>').append('\n'); + ss.append(generateXml(v, indent + 1, escape)); + ss.append(pad(indent)).append("</").append(key).append('>').append('\n'); + } else if (v instanceof List) { + List<Object> ll = (List<Object>) v; + for (Object o1 : ll) { + ss.append(pad(indent)).append('<').append(key).append('>').append('\n'); + ss.append(generateXml(o1, indent + 1, escape)); + ss.append(pad(indent)).append("</").append(key).append('>').append('\n'); + } + } + } + return ss.toString(); + } + + return null; + } + private static String generateJson(Object o, boolean escape, boolean quotes) { + if (o == null) { + return null; + } + if (o instanceof String && ((String) o).length() == 0) { + return null; + } + + StringBuilder ss = new StringBuilder(); + generateJson(ss, o, 0, false, escape, quotes); + return ss.toString(); + } + + @SuppressWarnings("unchecked") + private static void generateJson(StringBuilder ss, Object o, int indent, boolean padFirst, boolean escape, boolean quotes) { + if (o instanceof String) { + String s = escape ? StringEscapeUtils.escapeJson((String) o) : (String) o; + if (padFirst) { + ss.append(pad(indent)); + } + if (quotes) { + ss.append('"').append(s).append('"'); + } else { + ss.append(s); + } + return; + } + + if (o instanceof Map) { + Map<String, Object> mm = (Map<String, Object>) o; + + if (padFirst) { + ss.append(pad(indent)); + } + ss.append("{\n"); + + boolean first = true; + for (Map.Entry<String, Object> entry : mm.entrySet()) { + if (!first) { + ss.append(",\n"); + } + first = false; + Object v = entry.getValue(); + String key = entry.getKey(); + ss.append(pad(indent + 1)).append('"').append(key).append("\": "); + generateJson(ss, v, indent + 1, false, escape, true); + } + + ss.append("\n"); + ss.append(pad(indent)).append('}'); + + return; + } + + if (o instanceof List) { + List<Object> ll = (List<Object>) o; + + if (padFirst) { + ss.append(pad(indent)); + } + ss.append("[\n"); + + boolean first = true; + for (Object o1 : ll) { + if (!first) { + ss.append(",\n"); + } + first = false; + + generateJson(ss, o1, indent + 1, true, escape, quotes); + } + + ss.append("\n"); + ss.append(pad(indent)).append(']'); + } + } + + public static String removeLastCommaJson(String s) { + StringBuilder sb = new StringBuilder(); + int k = 0; + int start = 0; + while (k < s.length()) { + int i11 = s.indexOf('}', k); + int i12 = s.indexOf(']', k); + int i1 = -1; + if (i11 < 0) { + i1 = i12; + } else if (i12 < 0) { + i1 = i11; + } else { + i1 = i11 < i12 ? i11 : i12; + } + if (i1 < 0) { + break; + } + + int i2 = s.lastIndexOf(',', i1); + if (i2 < 0) { + k = i1 + 1; + continue; + } + + String between = s.substring(i2 + 1, i1); + if (between.trim().length() > 0) { + k = i1 + 1; + continue; + } + + sb.append(s.substring(start, i2)); + start = i2 + 1; + k = i1 + 1; + } + + sb.append(s.substring(start, s.length())); + + return sb.toString(); + } + + public static String removeEmptyStructJson(String template, String s) { + int k = 0; + while (k < s.length()) { + boolean curly = true; + int i11 = s.indexOf('{', k); + int i12 = s.indexOf('[', k); + int i1 = -1; + if (i11 < 0) { + i1 = i12; + curly = false; + } else if (i12 < 0) { + i1 = i11; + } else if (i11 < i12) { + i1 = i11; + } else { + i1 = i12; + curly = false; + } + + if (i1 >= 0) { + int i2 = curly ? s.indexOf('}', i1) : s.indexOf(']', i1); + if (i2 > 0) { + String value = s.substring(i1 + 1, i2); + if (value.trim().length() == 0) { + int i4 = s.lastIndexOf('\n', i1); + if (i4 < 0) { + i4 = 0; + } + int i5 = s.indexOf('\n', i2); + if (i5 < 0) { + i5 = s.length(); + } + + + /*If template mandates empty construct to be present, those should not be removed.*/ + if (template != null && template.contains(s.substring(i4))) { + k = i1 + 1; + } else { + s = s.substring(0, i4) + s.substring(i5); + k = 0; + } + } else { + k = i1 + 1; + } + } else { + break; + } + } else { + break; + } + } + + return s; + } + + public static String removeEmptyStructXml(String s) { + int k = 0; + while (k < s.length()) { + int i1 = s.indexOf('<', k); + if (i1 < 0 || i1 == s.length() - 1) { + break; + } + + char c1 = s.charAt(i1 + 1); + if (c1 == '?' || c1 == '!') { + k = i1 + 2; + continue; + } + + int i2 = s.indexOf('>', i1); + if (i2 < 0) { + k = i1 + 1; + continue; + } + + String closingTag = "</" + s.substring(i1 + 1, i2 + 1); + int i3 = s.indexOf(closingTag, i2 + 1); + if (i3 < 0) { + k = i2 + 1; + continue; + } + + String value = s.substring(i2 + 1, i3); + if (value.trim().length() > 0) { + k = i2 + 1; + continue; + } + + int i4 = s.lastIndexOf('\n', i1); + if (i4 < 0) { + i4 = 0; + } + int i5 = s.indexOf('\n', i3); + if (i5 < 0) { + i5 = s.length(); + } + + s = s.substring(0, i4) + s.substring(i5); + k = 0; + } + + return s; + } + + private static String pad(int n) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < n; i++) { + s.append(Character.toString('\t')); + } + return s.toString(); + } +} diff --git a/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/XmlParser.java b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/XmlParser.java new file mode 100644 index 000000000..42e9e57ad --- /dev/null +++ b/plugins/restapi-call-node/provider/src/main/java/org/onap/ccsdk/sli/plugins/restapicall/XmlParser.java @@ -0,0 +1,180 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : SDN-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * Modifications Copyright © 2018 IBM + * ================================================================================ + * 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 applicable 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.plugins.restapicall; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.XMLConstants; + +import org.onap.ccsdk.sli.core.sli.SvcLogicException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.XMLReader; + +public final class XmlParser { + + private static final Logger log = LoggerFactory.getLogger(XmlParser.class); + + private XmlParser() { + // Preventing instantiation of the same. + } + + public static Map<String, String> convertToProperties(String s, Set<String> listNameList) + throws SvcLogicException { + + checkNotNull(s, "Input should not be null."); + + Handler handler = new Handler(listNameList); + try { + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + InputStream in = new ByteArrayInputStream(s.getBytes()); + SAXParser saxParser = spf.newSAXParser(); + saxParser.parse(in, handler); + + } catch (ParserConfigurationException | IOException | SAXException | NumberFormatException e) { + throw new SvcLogicException("Unable to convert XML to properties" + e.getLocalizedMessage(), e); + } + return handler.getProperties(); + } + + private static class Handler extends DefaultHandler { + + private Set<String> listNameList; + + private Map<String, String> properties = new HashMap<>(); + + StringBuilder currentName = new StringBuilder(); + StringBuilder currentValue = new StringBuilder(); + + public Handler(Set<String> listNameList) { + super(); + this.listNameList = listNameList; + if (this.listNameList == null) + this.listNameList = new HashSet<>(); + } + + public Map<String, String> getProperties() { + return properties; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + super.startElement(uri, localName, qName, attributes); + + String name = localName; + if (name == null || name.trim().length() == 0) + name = qName; + int i2 = name.indexOf(':'); + if (i2 >= 0) + name = name.substring(i2 + 1); + + if (currentName.length() > 0) + currentName.append(Character.toString('.')); + currentName.append(name); + + String listName = removeIndexes(currentName.toString()); + + if (listNameList.contains(listName)) { + String n = currentName.toString() + "_length"; + int len = getInt(properties, n); + properties.put(n, String.valueOf(len + 1)); + currentName.append("[").append(len).append("]"); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + super.endElement(uri, localName, qName); + + String name = localName; + if (name == null || name.trim().length() == 0) + name = qName; + int i2 = name.indexOf(':'); + if (i2 >= 0) + name = name.substring(i2 + 1); + + String s = currentValue.toString().trim(); + if (s.length() > 0) { + properties.put(currentName.toString(), s); + + log.info("Added property: {} : {}", currentName, s); + currentValue = new StringBuilder(); + } + + int i1 = currentName.lastIndexOf("." + name); + if (i1 <= 0) + currentName = new StringBuilder(); + else + currentName = new StringBuilder(currentName.substring(0, i1)); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + super.characters(ch, start, length); + + String value = new String(ch, start, length); + currentValue.append(value); + } + + private static int getInt(Map<String, String> mm, String name) { + String s = mm.get(name); + if (s == null) + return 0; + return Integer.parseInt(s); + } + + private String removeIndexes(String currentName) { + StringBuilder b = new StringBuilder(); + boolean add = true; + for (int i = 0; i < currentName.length(); i++) { + char c = currentName.charAt(i); + if (c == '[') + add = false; + else if (c == ']') + add = true; + else if (add) + b.append(Character.toString(c)); + } + return b.toString(); + } + } +} |