/* * 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.pm; 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 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 generatePMBulkFileXml(List 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 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 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 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 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 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 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 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"; } }