aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/org/onap/a1pesimulator/service/fileready/PMBulkFileService.java
blob: 813c8a4c425e5b78787a904845d6cb6b70c259ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*
 * Copyright (C) 2021 Samsung Electronics
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package org.onap.a1pesimulator.service.fileready;

import static java.util.Comparator.comparing;
import static java.util.Objects.isNull;
import static org.onap.a1pesimulator.util.Constants.EMPTY_STRING;
import static org.onap.a1pesimulator.util.Constants.MEASUREMENT_FIELD_IDENTIFIER;
import static org.onap.a1pesimulator.util.Constants.TEMP_DIR;
import static org.onap.a1pesimulator.util.Convertors.ISO_8601_DATE;
import static org.onap.a1pesimulator.util.Convertors.YYYYMMDD_PATTERN;
import static org.onap.a1pesimulator.util.Convertors.truncateToSpecifiedMinutes;
import static org.onap.a1pesimulator.util.Convertors.zonedDateTimeToString;

import java.io.File;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.onap.a1pesimulator.data.fileready.EventMemoryHolder;
import org.onap.a1pesimulator.data.fileready.FileData;
import org.onap.a1pesimulator.data.ves.VesEvent;
import org.onap.a1pesimulator.util.VnfConfigReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import reactor.core.publisher.Mono;

/**
 * Service for PM Bulk File creation and handling
 */

@Service
public class PMBulkFileService {

    private static final Logger log = LoggerFactory.getLogger(PMBulkFileService.class);
    private static Map<String, AtomicInteger> uniqueFileNamesWithCount;
    private final VnfConfigReader vnfConfigReader;

    @Value("${xml.pm.bulk.fileFormatVersion}")
    private String fileFormatVersion;

    @Value("${xml.pm.bulk.vendorName}")
    private String vendorName;

    @Value("${xml.pm.bulk.fileSender}")
    private String fileSenderValue;

    @Value("${xml.pm.bulk.userLabel}")
    private String userLabel;

    @Value("${xml.pm.bulk.domainId}")
    private String domainId;

    public PMBulkFileService(VnfConfigReader vnfConfigReader) {
        this.vnfConfigReader = vnfConfigReader;
    }

    /**
     * Generate PM Bulk File xml from stored events
     *
     * @param collectedEvents list of stored events
     * @return generated file in Mono object
     */
    public Mono<FileData> generatePMBulkFileXml(List<EventMemoryHolder> collectedEvents) {

        try {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
            docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

            //root elements
            Document doc = docBuilder.newDocument();

            Element measCollecFile = doc.createElement("measCollecFile");
            doc.appendChild(measCollecFile);
            measCollecFile.setAttribute("xmlns", "http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec");
            measCollecFile.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            measCollecFile.setAttribute("xsi:schemaLocation",
                    "http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec");

            //fileHeader elements
            Element fileHeader = doc.createElement("fileHeader");
            measCollecFile.appendChild(fileHeader);
            fileHeader.setAttribute("fileFormatVersion", fileFormatVersion);
            fileHeader.setAttribute("vendorName", vendorName);

            //fileSender elements
            Element fileSender = doc.createElement("fileSender");
            fileHeader.appendChild(fileSender);
            fileSender.setAttribute("elementType", fileSenderValue);

            //measCollec elements
            Element measCollec = doc.createElement("measCollec");
            fileHeader.appendChild(measCollec);
            measCollec.setAttribute("beginTime", zonedDateTimeToString(earliestEventTime(collectedEvents), ISO_8601_DATE));

            //measData elements
            Element measData = doc.createElement("measData");
            measCollecFile.appendChild(measData);

            //managedElement elements
            Element managedElement = doc.createElement("managedElement");
            measData.appendChild(managedElement);
            managedElement.setAttribute("userLabel", userLabel);

            //add measInfo elements
            addMeansInfo(doc, measData, collectedEvents);

            //fileFooter elements
            Element fileFooter = doc.createElement("fileFooter");
            measCollecFile.appendChild(fileFooter);

            Element measCollecFooter = doc.createElement("measCollec");
            fileFooter.appendChild(measCollecFooter);
            measCollecFooter.setAttribute("endTime", zonedDateTimeToString(latestEventTime(collectedEvents), ISO_8601_DATE));

            File xmlFile = writeDocumentIntoXmlFile(doc, collectedEvents);

            Mono<FileData> justMono = Mono.just(FileData.builder().pmBulkFile(xmlFile).startEventDate(earliestEventTime(collectedEvents))
                    .endEventDate(latestEventTime(collectedEvents)).build());
            log.trace("Removing all VES events from memory: {}", collectedEvents.size());
            collectedEvents.clear();
            return justMono;

        } catch (ParserConfigurationException | TransformerException pce) {
            log.error("Error occurs while creating PM Bulk File", pce);
            return Mono.empty();
        }
    }

    /**
     * Add measurement elements for each cell and measurement time into PM Bulk File
     *
     * @param doc Document
     * @param measData main element of document, which stores meansData
     * @param collectedEvents list of stored events
     */
    private void addMeansInfo(Document doc, Element measData, List<EventMemoryHolder> collectedEvents) {
        collectedEvents.stream().sorted(comparing(EventMemoryHolder::getEventDate)).forEach(eventMemoryHolder -> {
            VesEvent event = eventMemoryHolder.getEvent();

            Element measInfo = doc.createElement("measInfo");
            measData.appendChild(measInfo);

            //job element
            Element job = doc.createElement("job");
            measInfo.appendChild(job);
            job.setAttribute("jobId", eventMemoryHolder.getJobId());

            //granPeriod elements
            Element granPeriod = doc.createElement("granPeriod");
            measInfo.appendChild(granPeriod);
            granPeriod.setAttribute("duration", getDurationString(eventMemoryHolder.getGranPeriod()));
            ZonedDateTime endDate = eventMemoryHolder.getEventDate();
            granPeriod.setAttribute("endTime", zonedDateTimeToString(endDate, ISO_8601_DATE));

            //repPeriod elements
            Element repPeriod = doc.createElement("repPeriod");
            measInfo.appendChild(repPeriod);
            repPeriod.setAttribute("duration", getDurationString(vnfConfigReader.getVnfConfig().getRepPeriod()));

            //measType definition
            HashMap<String, String> measurmentMap = new HashMap<>();
            AtomicInteger i = new AtomicInteger(1);
            event.getMeasurementFields().getAdditionalMeasurements().forEach(additionalMeasurement -> {
                if (Stream.of(MEASUREMENT_FIELD_IDENTIFIER)
                            .noneMatch(elementName -> elementName.equalsIgnoreCase(additionalMeasurement.getName()))) {
                    Element measType = doc.createElement("measType");
                    measInfo.appendChild(measType);
                    measType.setAttribute("p", String.valueOf(i));
                    measType.setTextContent(additionalMeasurement.getName());
                    measurmentMap.put(additionalMeasurement.getName(), String.valueOf(i));
                    i.incrementAndGet();
                }
            });

            //measValue elements
            Element measValue = doc.createElement("measValue");
            measInfo.appendChild(measValue);
            measValue.setAttribute("measObjLdn", eventMemoryHolder.getCellId());
            event.getMeasurementFields().getAdditionalMeasurements().stream()
                    .filter(additionalMeasurement -> measurmentMap.containsKey(additionalMeasurement.getName()))
                    .forEach(additionalMeasurement -> {
                        if (!additionalMeasurement.getMeasurementValue().isEmpty()) {

                            //r elements
                            Element r = doc.createElement("r");
                            measValue.appendChild(r);
                            r.setAttribute("p", measurmentMap.get(additionalMeasurement.getName()));
                            r.setTextContent(additionalMeasurement.getMeasurementValue());
                        }
                    });
        });
    }

    /**
     * Converts Document into XML file and adds proper headers
     *
     * @param doc Document
     * @param collectedEvents list of stored events
     * @return newly created File in xml format
     */
    private File writeDocumentIntoXmlFile(Document doc, List<EventMemoryHolder> collectedEvents) throws TransformerException {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

        Transformer tr = transformerFactory.newTransformer();
        tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        tr.setOutputProperty(OutputKeys.VERSION, "1.0");
        Node pi = doc.createProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"MeasDataCollection.xsl\"");
        doc.insertBefore(pi, doc.getDocumentElement());

        File xmlFile = getXmlFile(collectedEvents);
        StreamResult result = new StreamResult(xmlFile);
        DOMSource source = new DOMSource(doc);
        tr.transform(source, result);
        return xmlFile;
    }

    /**
     * Generate PM Bulk File and its name
     *
     * @param collectedEvents list of stored events
     * @return newly created File
     */
    private File getXmlFile(List<EventMemoryHolder> collectedEvents) {
        StringBuilder fileNameBuilder = new StringBuilder("C");
        ZonedDateTime firstEventTime = earliestEventTime(collectedEvents);
        ZonedDateTime lastEventTime = latestEventTime(collectedEvents);
        fileNameBuilder.append(zonedDateTimeToString(firstEventTime, YYYYMMDD_PATTERN)).append(".");
        fileNameBuilder.append(zonedDateTimeToString(truncateToSpecifiedMinutes(firstEventTime, 5), "HHmmZ")).append("-");
        fileNameBuilder.append(zonedDateTimeToString(lastEventTime, YYYYMMDD_PATTERN)).append(".");
        fileNameBuilder.append(zonedDateTimeToString(truncateToSpecifiedMinutes(lastEventTime, 5), "HHmmZ"));
        fileNameBuilder.append("_").append(domainId);
        fileNameBuilder.append(appendRcIfNecessary(fileNameBuilder));
        fileNameBuilder.append(".xml");

        return new File(TEMP_DIR, fileNameBuilder.toString());
    }

    /**
     * The RC parameter is a running count and shall be appended only if the filename is otherwise not unique, i.e. more than one file is generated and all
     * other parameters of the file name are identical.
     *
     * @param fileNameBuilder stringBuilder which contains currently generated file name
     * @return sequence number or empty string
     */
    private static String appendRcIfNecessary(StringBuilder fileNameBuilder) {
        String fileName = fileNameBuilder.toString();
        int sequence = 0;
        if (isNull(uniqueFileNamesWithCount)) {
            uniqueFileNamesWithCount = Collections.synchronizedMap(new HashMap<>());
        }
        if (uniqueFileNamesWithCount.containsKey(fileName)) {
            sequence = uniqueFileNamesWithCount.get(fileName).incrementAndGet();
        } else {
            uniqueFileNamesWithCount.clear(); //we have new dates, so we can clear existing list to not grow infinitely
            uniqueFileNamesWithCount.put(fileName, new AtomicInteger(0));
        }
        return sequence > 0 ? "_-_" + sequence : EMPTY_STRING;
    }

    /**
     * Get ZonedDateTime of the earliest event in that reporting period
     *
     * @param collectedEvents list of compared events
     * @return the earliest ZonedDateTime
     */
    private static ZonedDateTime earliestEventTime(List<EventMemoryHolder> collectedEvents) {
        return collectedEvents.stream()
                .map(EventMemoryHolder::getEventDate)
                .min(comparing(ZonedDateTime::toEpochSecond, Comparator.nullsLast(Comparator.naturalOrder())))
                .orElse(ZonedDateTime.now());
    }

    /**
     * Get ZonedDateTime of the latest event in that reporting period
     *
     * @param collectedEvents list of compared events
     * @return the latest ZonedDateTime
     */
    private static ZonedDateTime latestEventTime(List<EventMemoryHolder> collectedEvents) {
        return collectedEvents.stream().map(EventMemoryHolder::getEventDate)
                .max(comparing(ZonedDateTime::toEpochSecond, Comparator.nullsLast(Comparator.naturalOrder())))
                .orElse(ZonedDateTime.now());
    }

    /**
     * Convert duration interval in seconds to xml element required by the specification Examples: PT10S, PT900S
     *
     * @param interval interval in seconds
     * @return duration xml element representation
     */
    private static String getDurationString(int interval) {
        return "PT" + interval + "S";
    }
}