Replaced all tabs with spaces in java and pom.xml
[so.git] / adapters / mso-sdnc-adapter / src / main / java / org / onap / so / adapters / sdnc / sdncrest / SDNCConnector.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Modifications Copyright (C) 2018 IBM.
9  * Modifications Copyright (c) 2019 Samsung
10  * ================================================================================
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  * 
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  * 
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.so.adapters.sdnc.sdncrest;
26
27 import java.io.StringReader;
28 import java.net.HttpURLConnection;
29 import java.net.SocketTimeoutException;
30 import javax.xml.XMLConstants;
31 import javax.xml.bind.DatatypeConverter;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.xpath.XPath;
34 import javax.xml.xpath.XPathConstants;
35 import javax.xml.xpath.XPathExpressionException;
36 import javax.xml.xpath.XPathFactory;
37 import org.apache.http.HttpResponse;
38 import org.apache.http.client.HttpClient;
39 import org.apache.http.client.config.RequestConfig;
40 import org.apache.http.client.methods.HttpDelete;
41 import org.apache.http.client.methods.HttpGet;
42 import org.apache.http.client.methods.HttpPost;
43 import org.apache.http.client.methods.HttpPut;
44 import org.apache.http.client.methods.HttpRequestBase;
45 import org.apache.http.conn.ConnectTimeoutException;
46 import org.apache.http.entity.ContentType;
47 import org.apache.http.entity.StringEntity;
48 import org.apache.http.impl.client.HttpClientBuilder;
49 import org.apache.http.util.EntityUtils;
50 import org.onap.so.adapters.sdnc.impl.Constants;
51 import org.onap.so.adapters.sdncrest.SDNCErrorCommon;
52 import org.onap.so.adapters.sdncrest.SDNCResponseCommon;
53 import org.onap.so.logger.ErrorCode;
54 import org.onap.so.logger.MessageEnum;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.springframework.beans.factory.annotation.Autowired;
58 import org.springframework.stereotype.Component;
59 import org.w3c.dom.Document;
60 import org.w3c.dom.Element;
61 import org.w3c.dom.NodeList;
62 import org.xml.sax.InputSource;
63 import org.onap.so.utils.CryptoUtils;
64 import org.springframework.core.env.Environment;
65
66 /**
67  * Sends requests to SDNC and processes the responses.
68  */
69 @Component
70 public abstract class SDNCConnector {
71     private static final Logger logger = LoggerFactory.getLogger(SDNCConnector.class);
72
73     private static final String MSO_INTERNAL_ERROR = "MsoInternalError";
74     private static final String XPATH_EXCEPTION = "XPath Exception";
75     @Autowired
76     private Environment env;
77
78     public SDNCResponseCommon send(String content, TypedRequestTunables rt) {
79         logger.debug("SDNC URL: {}", rt.getSdncUrl());
80         logger.debug("SDNC Request Body:\n {}", content);
81
82         HttpRequestBase method = null;
83         HttpResponse httpResponse = null;
84
85         try {
86             int timeout = Integer.parseInt(rt.getTimeout());
87
88             RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout)
89                     .setConnectionRequestTimeout(timeout).build();
90
91             HttpClient client = HttpClientBuilder.create().build();
92
93             if ("POST".equals(rt.getReqMethod())) {
94                 HttpPost httpPost = new HttpPost(rt.getSdncUrl());
95                 httpPost.setConfig(requestConfig);
96                 httpPost.setEntity(new StringEntity(content, ContentType.APPLICATION_XML));
97                 method = httpPost;
98             } else if ("PUT".equals(rt.getReqMethod())) {
99                 HttpPut httpPut = new HttpPut(rt.getSdncUrl());
100                 httpPut.setConfig(requestConfig);
101                 httpPut.setEntity(new StringEntity(content, ContentType.APPLICATION_XML));
102                 method = httpPut;
103             } else if ("GET".equals(rt.getReqMethod())) {
104                 HttpGet httpGet = new HttpGet(rt.getSdncUrl());
105                 httpGet.setConfig(requestConfig);
106                 method = httpGet;
107             } else if ("DELETE".equals(rt.getReqMethod())) {
108                 HttpDelete httpDelete = new HttpDelete(rt.getSdncUrl());
109                 httpDelete.setConfig(requestConfig);
110                 method = httpDelete;
111             }
112
113
114             String userCredentials = CryptoUtils.decrypt(env.getProperty(Constants.SDNC_AUTH_PROP),
115                     env.getProperty(Constants.ENCRYPTION_KEY_PROP));
116             String authorization = "Basic " + DatatypeConverter.printBase64Binary(userCredentials.getBytes());
117             if (null != method) {
118                 method.setHeader("Authorization", authorization);
119                 method.setHeader("Accept", "application/yang.data+xml");
120             } else {
121                 logger.debug("method is NULL:");
122             }
123
124
125
126             httpResponse = client.execute(method);
127
128             String responseContent = null;
129             if (httpResponse.getEntity() != null) {
130                 responseContent = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
131             }
132
133             int statusCode = httpResponse.getStatusLine().getStatusCode();
134             String statusMessage = httpResponse.getStatusLine().getReasonPhrase();
135
136             logger.debug("SDNC Response: {} {}", statusCode,
137                     statusMessage + (responseContent == null ? "" : System.lineSeparator() + responseContent));
138
139             if (httpResponse.getStatusLine().getStatusCode() >= 300) {
140                 String errMsg = "SDNC returned " + statusCode + " " + statusMessage;
141
142                 String errors = analyzeErrors(responseContent);
143                 if (errors != null) {
144                     errMsg += " " + errors;
145                 }
146
147                 logError(errMsg);
148
149                 return createErrorResponse(statusCode, errMsg, rt);
150             }
151
152             httpResponse = null;
153
154             if (null != method) {
155                 method.reset();
156             } else {
157                 logger.debug("method is NULL:");
158             }
159
160             method = null;
161
162             logger.info("{} {} {}", MessageEnum.RA_RESPONSE_FROM_SDNC.toString(), responseContent, "SDNC");
163             return createResponseFromContent(statusCode, statusMessage, responseContent, rt);
164
165         } catch (SocketTimeoutException | ConnectTimeoutException e) {
166             String errMsg = "Request to SDNC timed out";
167             logError(errMsg, e);
168             return createErrorResponse(HttpURLConnection.HTTP_CLIENT_TIMEOUT, errMsg, rt);
169
170         } catch (Exception e) {
171             String errMsg = "Error processing request to SDNC";
172             logError(errMsg, e);
173             return createErrorResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, errMsg, rt);
174
175         } finally {
176             if (httpResponse != null) {
177                 try {
178                     EntityUtils.consume(httpResponse.getEntity());
179                 } catch (Exception e) {
180                     logger.debug("Exception:", e);
181                 }
182             }
183
184             if (method != null) {
185                 try {
186                     method.reset();
187                 } catch (Exception e) {
188                     logger.debug("Exception:", e);
189                 }
190             }
191         }
192     }
193
194     protected void logError(String errMsg) {
195         logger.error("{} {} {} {}", MessageEnum.RA_EXCEPTION_COMMUNICATE_SDNC.toString(), "SDNC",
196                 ErrorCode.AvailabilityError.getValue(), errMsg);
197     }
198
199     protected void logError(String errMsg, Throwable t) {
200         logger.error("{} {} {} {}", MessageEnum.RA_EXCEPTION_COMMUNICATE_SDNC.toString(), "SDNC",
201                 ErrorCode.AvailabilityError.getValue(), errMsg, t);
202     }
203
204     /**
205      * Generates a response object from content received from SDNC. The response object may be a success response object
206      * or an error response object. This method must be overridden by the subclass to return the correct object type.
207      * 
208      * @param statusCode the response status code from SDNC (e.g. 200)
209      * @param statusMessage the response status message from SDNC (e.g. "OK")
210      * @param responseContent the body of the response from SDNC (possibly null)
211      * @param rt request tunables
212      * @return a response object
213      */
214     protected abstract SDNCResponseCommon createResponseFromContent(int statusCode, String statusMessage,
215             String responseContent, TypedRequestTunables rt);
216
217     /**
218      * Generates an error response object. This method must be overridden by the subclass to return the correct object
219      * type.
220      * 
221      * @param statusCode the response status code (from SDNC, or internally generated)
222      * @param errMsg the error message (normally a verbose explanation of the error)
223      * @param rt request tunables
224      * @return an error response object
225      */
226     protected abstract SDNCErrorCommon createErrorResponse(int statusCode, String errMsg, TypedRequestTunables rt);
227
228     /**
229      * Called by the send() method to analyze errors that may be encoded in the content of non-2XX responses. By
230      * default, this method tries to parse the content as a restconf error.
231      * 
232      * <pre>
233      * xmlns = "urn:ietf:params:xml:ns:yang:ietf-restconf"
234      * </pre>
235      * 
236      * If an error (or errors) can be obtained from the content, then the result is a string in this format:
237      * 
238      * <pre>
239      * [error-type:TYPE, error-tag:TAG, error-message:MESSAGE] ...
240      * </pre>
241      * 
242      * If no error could be obtained from the content, then the result is null.
243      * <p>
244      * The subclass can override this method to provide another implementation.
245      */
246     protected String analyzeErrors(String content) {
247         if (content == null || content.isEmpty()) {
248             return null;
249         }
250
251         // Confirmed with Andrew Shen on 11/1/16 that SDNC will send content like
252         // this in error responses (non-2XX response codes) to "agnostic" service
253         // requests.
254         //
255         // <errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
256         // <error>
257         // <error-type>protocol</error-type>
258         // <error-tag>malformed-message</error-tag>
259         // <error-message>Error parsing input: The element type "input" must be terminated by the matching end-tag
260         // "&lt;/input&gt;".</error-message>
261         // </error>
262         // </errors>
263
264         StringBuilder output = null;
265
266         try {
267             XPathFactory xpathFactory = XPathFactory.newInstance();
268             XPath xpath = xpathFactory.newXPath();
269             DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
270             documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
271             documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
272             documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
273             InputSource source = new InputSource(new StringReader(content));
274             Document doc = documentBuilderFactory.newDocumentBuilder().parse(source);
275             NodeList errors = (NodeList) xpath.evaluate("errors/error", doc, XPathConstants.NODESET);
276
277             for (int i = 0; i < errors.getLength(); i++) {
278                 Element error = (Element) errors.item(i);
279
280                 String info = "";
281
282                 try {
283                     String errorType = xpath.evaluate("error-type", error);
284                     info += "error-type:" + errorType;
285                 } catch (XPathExpressionException e) {
286                     logger.error("{} {} {} {} {} {}", MessageEnum.RA_EVALUATE_XPATH_ERROR.toString(), "error-type",
287                             error.toString(), "SDNC", ErrorCode.DataError.getValue(), XPATH_EXCEPTION, e);
288                 }
289
290                 try {
291                     String errorTag = xpath.evaluate("error-tag", error);
292                     if (!info.isEmpty()) {
293                         info += ", ";
294                     }
295                     info += "error-tag:" + errorTag;
296                 } catch (XPathExpressionException e) {
297                     logger.error("{} {} {} {} {} {}", MessageEnum.RA_EVALUATE_XPATH_ERROR.toString(), "error-tag",
298                             error.toString(), "SDNC", ErrorCode.DataError.getValue(), XPATH_EXCEPTION, e);
299                 }
300
301                 try {
302                     String errorMessage = xpath.evaluate("error-message", error);
303                     if (!info.isEmpty()) {
304                         info += ", ";
305                     }
306                     info += "error-message:" + errorMessage;
307                 } catch (Exception e) {
308                     logger.error("{} {} {} {} {} {}", MessageEnum.RA_EVALUATE_XPATH_ERROR.toString(), "error-message",
309                             error.toString(), "SDNC", ErrorCode.DataError.getValue(), XPATH_EXCEPTION, e);
310                 }
311
312                 if (!info.isEmpty()) {
313                     if (output == null) {
314                         output = new StringBuilder("[" + info + "]");
315                     } else {
316                         output.append(" [").append(info).append("]");
317                     }
318                 }
319             }
320         } catch (Exception e) {
321             logger.error("{} {} {} {}", MessageEnum.RA_ANALYZE_ERROR_EXC.toString(), "SDNC",
322                     ErrorCode.DataError.getValue(), "Exception while analyzing errors", e);
323         }
324
325         return output.toString();
326     }
327 }