8b25e47336edef0cfbf87bb766aacee0ea618f68
[sdnc/apps.git] /
1 /*
2  * ============LICENSE_START===================================================
3  * Copyright (c) 2018 Amdocs
4  * ============================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  * ============LICENSE_END=====================================================
17  */
18 package org.onap.sdnc.apps.pomba.networkdiscovery.service;
19
20 import com.google.common.util.concurrent.ThreadFactoryBuilder;
21 import java.io.IOException;
22 import java.io.StringReader;
23 import java.text.MessageFormat;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import javax.annotation.PreDestroy;
31 import javax.ws.rs.client.Client;
32 import javax.ws.rs.client.Entity;
33 import javax.ws.rs.client.Invocation;
34 import javax.ws.rs.core.HttpHeaders;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.MultivaluedHashMap;
37 import javax.ws.rs.core.MultivaluedMap;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.Response.Status;
40 import javax.xml.parsers.DocumentBuilder;
41 import javax.xml.parsers.DocumentBuilderFactory;
42 import javax.xml.parsers.ParserConfigurationException;
43 import org.onap.aai.restclient.client.OperationResult;
44 import org.onap.aai.restclient.client.RestClient;
45 import org.onap.logging.ref.slf4j.ONAPLogAdapter;
46 import org.onap.logging.ref.slf4j.ONAPLogAdapter.RequestBuilder;
47 import org.onap.logging.ref.slf4j.ONAPLogConstants.InvocationMode;
48 import org.onap.sdnc.apps.pomba.networkdiscovery.ApplicationException;
49 import org.onap.sdnc.apps.pomba.networkdiscovery.datamodel.Attribute;
50 import org.onap.sdnc.apps.pomba.networkdiscovery.datamodel.DataQuality;
51 import org.onap.sdnc.apps.pomba.networkdiscovery.datamodel.NetworkDiscoveryNotification;
52 import org.onap.sdnc.apps.pomba.networkdiscovery.datamodel.NetworkDiscoveryResponse;
53 import org.onap.sdnc.apps.pomba.networkdiscovery.datamodel.Resource;
54 import org.onap.sdnc.apps.pomba.networkdiscovery.service.rs.RestService;
55 import org.slf4j.Logger;
56 import org.springframework.beans.factory.annotation.Autowired;
57 import org.springframework.stereotype.Service;
58 import org.w3c.dom.Document;
59 import org.w3c.dom.Element;
60 import org.w3c.dom.Node;
61 import org.w3c.dom.NodeList;
62 import org.xml.sax.InputSource;
63 import org.xml.sax.SAXException;
64
65 @Service
66 public class SpringServiceImpl implements SpringService {
67     private static final String ENRICHER_HEADER_APPLICATION = "X-FromAppId";
68     private static final String ENRICHER_HEADER_TRANSACTION = "X-TransactionId";
69
70     private static final int DEFAULT_WORKER_THREADS = 3;
71
72     private ExecutorService executor = Executors.newFixedThreadPool(
73                     Integer.getInteger("discovery.threads", DEFAULT_WORKER_THREADS),
74                     new ThreadFactoryBuilder().setNameFormat("discovery-worker-%d").build());
75
76     @Autowired
77     private RestClient enricherClient;
78
79     @Autowired
80     private String enricherBaseUrl;
81
82     @javax.annotation.Resource
83     private Client callbackClient;
84
85     @javax.annotation.Resource
86     private Map<String, String> enricherTypeURLs;
87
88     private DocumentBuilder parser;
89
90     @javax.annotation.Resource
91     private Map<String, String> enricherAttributeNameMapping;
92
93     public SpringServiceImpl() throws ParserConfigurationException {
94         this.parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
95     }
96
97     @Override
98     public NetworkDiscoveryResponse findbyResourceIdAndType(String transactionId,
99                     String requestId,
100                     String resourceType,
101                     List<String> resourceIds,
102                     String notificationURL,
103                     String notificationAuthorization,
104                     ONAPLogAdapter adapter) throws ApplicationException {
105
106         NetworkDiscoveryResponse response = new NetworkDiscoveryResponse();
107         response.setRequestId(requestId);
108
109         String enricherURL = this.enricherTypeURLs.get(resourceType);
110         // check if resourceType is supported
111         if (enricherURL == null) {
112             // don't know what to do with this - return empty response
113             response.setCode(Status.NO_CONTENT.getStatusCode());
114             response.setMessage("Unsupported resourceType " + resourceType);
115             response.setAckFinalIndicator(true);
116             return response;
117         }
118
119         // schedule discovery of specified resources
120         Runnable task = new ResourceTask(transactionId, requestId, resourceType, resourceIds, notificationURL,
121                         notificationAuthorization, enricherURL, adapter);
122         this.executor.submit(task);
123
124         response.setCode(Status.ACCEPTED.getStatusCode());
125         response.setMessage(Status.ACCEPTED.getReasonPhrase());
126         response.setAckFinalIndicator(false);
127         return response;
128     }
129
130     @PreDestroy
131     public void shutdown() {
132         this.executor.shutdown();
133     }
134
135     private List<Attribute> toAttributeList(String xml, ONAPLogAdapter adapter) throws SAXException, IOException {
136         Logger log = adapter.unwrap();
137         Document doc = this.parser.parse(new InputSource(new StringReader(xml)));
138         NodeList children = doc.getDocumentElement().getChildNodes();
139         List<Attribute> result = new ArrayList<>();
140         for (int i = 0; i < children.getLength(); i++) {
141             Node child = children.item(i);
142             if (child.getNodeType() == Node.ELEMENT_NODE) {
143
144                 // remove white space before conversion
145                 String attributeName = ((Element) child).getTagName().replaceAll("\\s", "");
146
147                 // If the incoming attribute name is not listed in the
148                 // attributeNameMapping, then this attribute will be removed.
149                 String newName = enricherAttributeNameMapping.get(attributeName);
150                 if (newName != null) {
151                     Attribute attr = new Attribute();
152                     attr.setName(newName);
153                     attr.setValue(((Element) child).getTextContent());
154                     attr.setDataQuality(DataQuality.ok());
155                     result.add(attr);
156                 } else {
157                     log.debug("[" + ((Element) child).getTagName()
158                                     + "] was removed due to not listed in enricherAttributeNameMapping.");
159                 }
160             }
161         }
162         return result;
163     }
164
165     private void sendNotification(String url, String authorization, Object notification, ONAPLogAdapter adapter) {
166
167         Invocation.Builder request = this.callbackClient.target(url).request().accept(MediaType.TEXT_PLAIN_TYPE);
168
169         if (authorization != null) {
170             request.header(HttpHeaders.AUTHORIZATION, authorization);
171         }
172         Logger log = adapter.unwrap();
173         adapter.invoke(new RequestBuilderWrapper(request), InvocationMode.SYNCHRONOUS);
174         try {
175             // native client marshaller doesn't skip null fields
176             // so manually marshal notification to json
177
178             if (log.isDebugEnabled()) {
179                 StringBuilder debugRequest = new StringBuilder("REQUEST:\n");
180                 debugRequest.append("URL: ").append(url).append("\n");
181                 debugRequest.append("Payload: ").append(notification).append("\n");
182                 // if (headers != null) {
183                 // debugRequest.append("Headers: ");
184                 // for (Entry<String, List<String>> header : headers.entrySet())
185                 // {
186                 // debugRequest.append("\n\t").append(header.getKey()).append(":");
187                 // for (String headerEntry : header.getValue()) {
188                 // debugRequest.append("\"").append(headerEntry).append("\" ");
189                 // }
190                 // }
191                 // }
192                 log.debug(debugRequest.toString());
193             }
194
195             Response result = request.post(Entity.json(notification));
196
197             adapter.unwrap().info("request at url = {} resulted in http response: {}", url,
198                             result.getStatusInfo().getStatusCode() + " " + result.getStatusInfo().getReasonPhrase());
199
200             if (log.isDebugEnabled()) {
201                 StringBuilder debugResponse = new StringBuilder("RESPONSE:\n");
202                 debugResponse.append("Result: ").append(result.getStatus()).append("\n");
203                 String content = result.hasEntity() ? result.getEntity().toString() : null;
204                 if (result.getStatus() >= 300) {
205                     debugResponse.append("Failure Cause: ").append(content).append("\n");
206                 } else {
207                     debugResponse.append("Payload: ").append(content).append("\n");
208                 }
209                 if (result.getHeaders() != null) {
210                     debugResponse.append("Headers: ");
211                     for (Entry<String, List<Object>> header : result.getHeaders().entrySet()) {
212                         debugResponse.append("\n\t").append(header.getKey()).append(":");
213                         for (Object headerEntry : header.getValue()) {
214                             debugResponse.append("\"").append(headerEntry).append("\" ");
215                         }
216                     }
217                 }
218                 log.debug(debugResponse.toString());
219             }
220
221         } catch (Exception x) {
222             log.error("Error during {} operation to endpoint at url = {} with error = {}", "POST", url,
223                             x.getLocalizedMessage());
224         }
225     }
226
227     private class ResourceTask implements Runnable {
228         private final String transactionId;
229         private final String requestId;
230         private final String resourceType;
231         private final List<String> resourceIds;
232         private final String notificationURL;
233         private final String notificationAuthorization;
234         private final String resourceURL;
235         private final ONAPLogAdapter adapter;
236
237         public ResourceTask(String transactionId,
238                         String requestId,
239                         String resourceType,
240                         List<String> resourceIds,
241                         String notificationURL,
242                         String notificationAuthorization,
243                         String resourceURL,
244                         ONAPLogAdapter adapter) {
245             this.transactionId = transactionId;
246             this.requestId = requestId;
247             this.resourceType = resourceType;
248             this.resourceIds = resourceIds;
249             this.notificationURL = notificationURL;
250             this.notificationAuthorization = notificationAuthorization;
251             this.resourceURL = resourceURL;
252             this.adapter = adapter;
253         }
254
255         @Override
256         public void run() {
257             NetworkDiscoveryNotification notification = new NetworkDiscoveryNotification();
258             notification.setRequestId(this.requestId);
259
260             List<Resource> resources = null;
261             MessageFormat format = new MessageFormat(SpringServiceImpl.this.enricherBaseUrl + this.resourceURL);
262             MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
263             headers.add(ENRICHER_HEADER_APPLICATION, RestService.SERVICE_NAME);
264             headers.add(ENRICHER_HEADER_TRANSACTION, this.transactionId);
265
266             for (String resourceId : this.resourceIds) {
267                 String url = format.format(new Object[] { resourceId });
268                 OperationResult result = SpringServiceImpl.this.enricherClient.get(url, headers,
269                                 MediaType.APPLICATION_XML_TYPE);
270
271                 Resource resource = new Resource();
272                 resource.setType(this.resourceType);
273                 resource.setId(resourceId);
274                 if (resources == null) {
275                     resources = new ArrayList<>();
276                     notification.setResources(resources);
277                 }
278                 resources.add(resource);
279
280                 if (result.wasSuccessful()) {
281                     resource.setDataQuality(DataQuality.ok());
282                     try {
283                         List<Attribute> attributeList = toAttributeList(result.getResult(), adapter);
284                         resource.setAttributeList(attributeList);
285                     } catch (Exception x) {
286                         resource.setDataQuality(DataQuality.error(x.getMessage()));
287                     }
288                 } else {
289                     resource.setDataQuality(DataQuality.error(result.getFailureCause()));
290                 }
291             }
292             notification.setCode(Status.OK.getStatusCode());
293             notification.setMessage(Status.OK.getReasonPhrase());
294             notification.setAckFinalIndicator(true);
295
296             // call client back with resource details
297             sendNotification(this.notificationURL, this.notificationAuthorization, notification, adapter);
298         }
299     }
300
301     private static class RequestBuilderWrapper implements RequestBuilder<RequestBuilderWrapper> {
302         private Invocation.Builder builder;
303
304         private RequestBuilderWrapper(Invocation.Builder builder) {
305             this.builder = builder;
306         }
307
308         @Override
309         public RequestBuilderWrapper setHeader(String name, String value) {
310             this.builder.header(name, value);
311             return this;
312         }
313
314     }
315 }