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