Return List<Artifact> in ArtifactDownloadManager
[aai/model-loader.git] / src / main / java / org / onap / aai / modelloader / entity / catalog / VnfCatalogArtifactHandler.java
1 /**\r
2  * ============LICENSE_START=======================================================\r
3  * org.onap.aai\r
4  * ================================================================================\r
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.\r
6  * Copyright © 2017-2018 European Software Marketing Ltd.\r
7  * ================================================================================\r
8  * Licensed under the Apache License, Version 2.0 (the "License");\r
9  * you may not use this file except in compliance with the License.\r
10  * You may obtain a copy of the License at\r
11  *\r
12  *       http://www.apache.org/licenses/LICENSE-2.0\r
13  *\r
14  * Unless required by applicable law or agreed to in writing, software\r
15  * distributed under the License is distributed on an "AS IS" BASIS,\r
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
17  * See the License for the specific language governing permissions and\r
18  * limitations under the License.\r
19  * ============LICENSE_END=========================================================\r
20  */\r
21 package org.onap.aai.modelloader.entity.catalog;\r
22 \r
23 import com.google.gson.Gson;\r
24 import com.google.gson.reflect.TypeToken;\r
25 import java.io.StringReader;\r
26 import java.net.URISyntaxException;\r
27 import java.util.ArrayList;\r
28 import java.util.HashMap;\r
29 import java.util.List;\r
30 import java.util.Map;\r
31 import java.util.Map.Entry;\r
32 import java.util.UUID;\r
33 import javax.xml.parsers.DocumentBuilder;\r
34 import javax.xml.parsers.DocumentBuilderFactory;\r
35 import org.apache.commons.text.StringEscapeUtils;\r
36 import org.apache.http.client.utils.URIBuilder;\r
37 import org.onap.aai.cl.api.Logger;\r
38 import org.onap.aai.cl.eelf.LoggerFactory;\r
39 import org.onap.aai.modelloader.config.ModelLoaderConfig;\r
40 import org.onap.aai.modelloader.entity.Artifact;\r
41 import org.onap.aai.modelloader.entity.ArtifactHandler;\r
42 import org.onap.aai.modelloader.entity.vnf.VnfImages;\r
43 import org.onap.aai.modelloader.restclient.AaiRestClient;\r
44 import org.onap.aai.modelloader.service.ModelLoaderMsgs;\r
45 import org.springframework.http.HttpStatus;\r
46 import org.springframework.http.MediaType;\r
47 import org.springframework.http.ResponseEntity;\r
48 import org.springframework.stereotype.Component;\r
49 import org.springframework.web.client.RestTemplate;\r
50 import org.w3c.dom.Document;\r
51 import org.w3c.dom.Element;\r
52 import org.w3c.dom.Node;\r
53 import org.w3c.dom.NodeList;\r
54 import org.xml.sax.InputSource;\r
55 \r
56 /**\r
57  * VNF Catalog specific handling\r
58  */\r
59 @Component\r
60 public class VnfCatalogArtifactHandler extends ArtifactHandler {\r
61 \r
62     private static Logger logger = LoggerFactory.getInstance().getLogger(VnfCatalogArtifactHandler.class.getName());\r
63 \r
64     public static final String ATTR_UUID = "uuid";\r
65 \r
66     public VnfCatalogArtifactHandler(ModelLoaderConfig config) {\r
67         super(config);\r
68     }\r
69 \r
70     /*\r
71      * (non-Javadoc)\r
72      * \r
73      * @see org.openecomp.modelloader.entity.ArtifactHandler#pushArtifacts(java.util.List, java.lang.String)\r
74      */\r
75     @Override\r
76     public boolean pushArtifacts(List<Artifact> artifacts, String distributionId, List<Artifact> completedArtifacts,\r
77             AaiRestClient aaiClient) {\r
78         for (Artifact artifact : artifacts) {\r
79             try {\r
80                 distributeVnfcData(aaiClient, distributionId, artifact, completedArtifacts);\r
81             } catch (VnfImageException e) {\r
82                 if (e.getResultCode().isPresent()) {\r
83                     logger.error(ModelLoaderMsgs.DISTRIBUTION_EVENT_ERROR,\r
84                             "Ingestion failed on vnf-image " + e.getImageId() + " with status "\r
85                                     + e.getResultCode().orElse(0) + ". Rolling back distribution.");\r
86                 } else {\r
87                     logger.error(ModelLoaderMsgs.DISTRIBUTION_EVENT_ERROR,\r
88                             "Ingestion failed on " + e.getImageId() + ". Rolling back distribution.");\r
89                 }\r
90                 return false;\r
91             }\r
92         }\r
93 \r
94         return true;\r
95     }\r
96 \r
97     /*\r
98      * If something fails in the middle of ingesting the catalog we want to roll back any changes to the DB\r
99      */\r
100     @Override\r
101     public void rollback(List<Artifact> completedArtifacts, String distributionId, AaiRestClient aaiClient) {\r
102         for (Artifact completedArtifact : completedArtifacts) {\r
103             Map<String, String> data = new Gson().fromJson(completedArtifact.getPayload(),\r
104                     new TypeToken<Map<String, String>>() {}.getType());\r
105             String url = config.getAaiBaseUrl() + config.getAaiVnfImageUrl() + "/vnf-image/" + data.get(ATTR_UUID);\r
106             // Try to delete the image. If something goes wrong we can't really do anything here\r
107             aaiClient.getAndDeleteResource(url, distributionId);\r
108         }\r
109     }\r
110 \r
111     private void distributeVnfcData(AaiRestClient restClient, String distributionId, Artifact vnfcArtifact,\r
112             List<Artifact> completedArtifacts) throws VnfImageException {\r
113         List<Map<String, String>> vnfcData;\r
114         switch (vnfcArtifact.getType()) {\r
115             case VNF_CATALOG:\r
116                 vnfcData = unmarshallVnfcData(vnfcArtifact);\r
117                 break;\r
118             case VNF_CATALOG_XML:\r
119                 vnfcData = parseXmlVnfcData(vnfcArtifact);\r
120                 break;\r
121             default:\r
122                 throw new VnfImageException("Unsupported type " + vnfcArtifact.getType());\r
123         }\r
124         distributeVnfcData(restClient, distributionId, completedArtifacts, vnfcData);\r
125     }\r
126 \r
127     /**\r
128      * Build a VNF image from each of the supplied data items, and distribute to AAI\r
129      * \r
130      * @param restClient\r
131      * @param distributionId\r
132      * @param completedArtifacts\r
133      * @param vnfcData\r
134      * @throws VnfImageException\r
135      */\r
136     private void distributeVnfcData(AaiRestClient restClient, String distributionId, List<Artifact> completedArtifacts,\r
137             List<Map<String, String>> vnfcData) throws VnfImageException {\r
138         for (Map<String, String> dataItem : vnfcData) {\r
139             // If an empty dataItem is supplied, do nothing.\r
140             if (dataItem.isEmpty()) {\r
141                 logger.warn(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Empty image data supplied, skipping ingestion.");\r
142                 continue;\r
143             }\r
144 \r
145             StringBuilder imageIdBuilder = new StringBuilder("vnf image");\r
146             for (Entry<String, String> entry : dataItem.entrySet()) {\r
147                 imageIdBuilder.append(" ").append(entry.getValue());\r
148             }\r
149             String imageId = imageIdBuilder.toString();\r
150             int resultCode = getVnfImage(restClient, distributionId, imageId, dataItem);\r
151 \r
152             if (resultCode == HttpStatus.NOT_FOUND.value()) {\r
153                 // This vnf-image is missing, so add it\r
154                 boolean success = putVnfImage(restClient, dataItem, distributionId);\r
155                 if (success) {\r
156                     completedArtifacts.add(new VnfCatalogArtifact(new Gson().toJson(dataItem)));\r
157                     logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, imageId + " successfully ingested.");\r
158                 } else {\r
159                     throw new VnfImageException(imageId);\r
160                 }\r
161             } else if (resultCode == HttpStatus.OK.value()) {\r
162                 logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, imageId + " already exists. Skipping ingestion.");\r
163             } else {\r
164                 // if other than 404 or 200, something went wrong\r
165                 throw new VnfImageException(imageId, resultCode);\r
166             }\r
167         }\r
168     }\r
169 \r
170     private int getVnfImage(AaiRestClient restClient, String distributionId, String imageId,\r
171             Map<String, String> dataItem) throws VnfImageException {\r
172         try {\r
173             URIBuilder b = new URIBuilder(config.getAaiBaseUrl() + config.getAaiVnfImageUrl());\r
174             for (Entry<String, String> entry : dataItem.entrySet()) {\r
175                 b.addParameter(entry.getKey(), entry.getValue());\r
176             }\r
177             ResponseEntity<VnfImages> tryGet =\r
178                     restClient.getResource(b.build().toString(), distributionId, MediaType.APPLICATION_JSON, VnfImages.class);\r
179             if (tryGet == null) {\r
180                 throw new VnfImageException(imageId);\r
181             }\r
182             return tryGet.getStatusCodeValue();\r
183         } catch (URISyntaxException ex) {\r
184             throw new VnfImageException(ex);\r
185         }\r
186     }\r
187 \r
188     private boolean putVnfImage(AaiRestClient restClient, Map<String, String> dataItem, String distributionId) {\r
189         // Generate a new UUID for the image data item\r
190         String uuid = UUID.randomUUID().toString();\r
191         dataItem.put(ATTR_UUID, uuid);\r
192 \r
193         // TODO: Get rid of the dataItem map and replace it with the VnfImage object\r
194         String payload = new Gson().toJson(dataItem);\r
195         String putUrl = config.getAaiBaseUrl() + config.getAaiVnfImageUrl() + "/vnf-image/" + uuid;\r
196         ResponseEntity<String> putResp =\r
197                 restClient.putResource(putUrl, payload, distributionId, MediaType.APPLICATION_JSON, String.class);\r
198         return putResp != null && putResp.getStatusCode() == HttpStatus.CREATED;\r
199     }\r
200 \r
201     private List<Map<String, String>> unmarshallVnfcData(Artifact vnfcArtifact) {\r
202         // Unmarshall Babel JSON payload into a List of Maps of JSON attribute name/values.\r
203         return new Gson().fromJson(StringEscapeUtils.unescapeJson(vnfcArtifact.getPayload()),\r
204                 new TypeToken<List<Map<String, String>>>() {}.getType());\r
205     }\r
206 \r
207     /**\r
208      * Parse the VNF Catalog XML and transform into Key/Value pairs.\r
209      * \r
210      * @param vnfcArtifact\r
211      * @return VNF Image data in Map form\r
212      * @throws VnfImageException\r
213      */\r
214     private List<Map<String, String>> parseXmlVnfcData(Artifact vnfcArtifact) throws VnfImageException {\r
215         List<Map<String, String>> vnfcData = new ArrayList<>();\r
216         try {\r
217             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\r
218             factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);\r
219             DocumentBuilder builder = factory.newDocumentBuilder();\r
220             InputSource is = new InputSource(new StringReader(vnfcArtifact.getPayload()));\r
221             Document doc = builder.parse(is);\r
222             doc.getDocumentElement().normalize();\r
223 \r
224             NodeList pnl = doc.getElementsByTagName("part-number-list");\r
225             for (int i = 0; i < pnl.getLength(); i++) {\r
226                 Node partNumber = pnl.item(i);\r
227                 if (partNumber.getNodeType() == Node.ELEMENT_NODE) {\r
228                     Element vendorInfo = getFirstChildNodeByName(partNumber, "vendor-info");\r
229                     if (vendorInfo != null) {\r
230                         Map<String, String> application = new HashMap<>();\r
231                         application.put("application",\r
232                                 vendorInfo.getElementsByTagName("vendor-model").item(0).getTextContent());\r
233                         application.put("application-vendor",\r
234                                 vendorInfo.getElementsByTagName("vendor-name").item(0).getTextContent());\r
235                         populateSoftwareVersions(vnfcData, application, partNumber);\r
236                     }\r
237                 }\r
238             }\r
239         } catch (Exception ex) {\r
240             throw new VnfImageException(ex);\r
241         }\r
242         return vnfcData;\r
243     }\r
244 \r
245     /**\r
246      * @param vnfcData to populate\r
247      * @param applicationData\r
248      * @param partNumber\r
249      */\r
250     private void populateSoftwareVersions(List<Map<String, String>> vnfcData, Map<String, String> applicationData,\r
251             Node partNumber) {\r
252         NodeList nodes = partNumber.getChildNodes();\r
253         for (int i = 0; i < nodes.getLength(); i++) {\r
254             Node childNode = nodes.item(i);\r
255             if (childNode.getNodeName().equalsIgnoreCase("software-version-list")) {\r
256                 Element softwareVersion = getFirstChildNodeByName(childNode, "software-version");\r
257                 if (softwareVersion != null) {\r
258                     HashMap<String, String> vnfImageData = new HashMap<>(applicationData);\r
259                     vnfImageData.put("application-version", softwareVersion.getTextContent());\r
260                     vnfcData.add(vnfImageData);\r
261                 }\r
262             }\r
263         }\r
264     }\r
265 \r
266     /**\r
267      * @param node\r
268      * @param childNodeName\r
269      * @return the first child node matching the given name\r
270      */\r
271     private Element getFirstChildNodeByName(Node node, String childNodeName) {\r
272         NodeList nodes = node.getChildNodes();\r
273         for (int i = 0; i < nodes.getLength(); i++) {\r
274             Node childNode = nodes.item(i);\r
275             if (childNode.getNodeName().equalsIgnoreCase(childNodeName)) {\r
276                 return (Element) childNode;\r
277             }\r
278         }\r
279         return null;\r
280     }\r
281 \r
282 }\r