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