2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2019-2020 Nordix Foundation.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.apex.model.basicmodel.handling;
24 import java.io.OutputStream;
25 import java.io.OutputStreamWriter;
26 import java.io.Writer;
28 import java.util.TreeSet;
30 import javax.xml.XMLConstants;
31 import javax.xml.bind.JAXBContext;
32 import javax.xml.bind.JAXBException;
33 import javax.xml.bind.Marshaller;
34 import javax.xml.parsers.DocumentBuilderFactory;
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.transform.OutputKeys;
37 import javax.xml.transform.Transformer;
38 import javax.xml.transform.TransformerConfigurationException;
39 import javax.xml.transform.TransformerException;
40 import javax.xml.transform.TransformerFactory;
41 import javax.xml.transform.dom.DOMSource;
42 import javax.xml.transform.stream.StreamResult;
44 import org.eclipse.persistence.jaxb.JAXBContextFactory;
45 import org.eclipse.persistence.jaxb.MarshallerProperties;
46 import org.eclipse.persistence.oxm.MediaType;
47 import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
48 import org.onap.policy.apex.model.basicmodel.concepts.AxValidationResult;
49 import org.onap.policy.common.utils.validation.Assertions;
50 import org.slf4j.ext.XLogger;
51 import org.slf4j.ext.XLoggerFactory;
52 import org.w3c.dom.Document;
55 * This class writes an Apex concept to an XML file or JSON file from a Java Apex Concept.
57 * @param <C> the type of Apex concept to write, must be a sub class of {@link AxConcept}
58 * @author John Keeney (john.keeney@ericsson.com)
60 public class ApexModelWriter<C extends AxConcept> {
62 private static final String CONCEPT_MAY_NOT_BE_NULL = "concept may not be null";
63 private static final String CONCEPT_WRITER_MAY_NOT_BE_NULL = "concept writer may not be null";
64 private static final String CONCEPT_STREAM_MAY_NOT_BE_NULL = "concept stream may not be null";
66 // Get a reference to the logger
67 private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexModelWriter.class);
69 // Writing as JSON or XML
70 private boolean jsonOutput = false;
72 // The list of fields to output as CDATA
73 private final Set<String> cdataFieldSet = new TreeSet<>();
75 // The Marshaller for the Apex concepts
76 private Marshaller marshaller = null;
78 // All written concepts are validated before writing if this flag is set
79 private boolean validateFlag = true;
82 * Constructor, initiates the writer.
84 * @param rootConceptClass the root concept class for concept reading
85 * @throws ApexModelException the apex concept writer exception
87 public ApexModelWriter(final Class<C> rootConceptClass) throws ApexModelException {
88 // Set up Eclipselink for XML and JSON output
89 System.setProperty("javax.xml.bind.context.factory", "org.eclipse.persistence.jaxb.JAXBContextFactory");
92 final JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[]{rootConceptClass}, null);
94 // Set up the unmarshaller to carry out validation
95 marshaller = jaxbContext.createMarshaller();
96 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
97 marshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
98 } catch (final JAXBException e) {
99 throw new ApexModelException("JAXB marshaller creation exception", e);
104 * The set of fields to be output as CDATA.
106 * @return the set of fields
108 public Set<String> getCDataFieldSet() {
109 return cdataFieldSet;
113 * Return true if JSON output enabled, XML output if false.
115 * @return true for JSON output
117 public boolean isJsonOutput() {
122 * Set the value of JSON output, true for JSON output, false for XML output.
124 * @param jsonOutput true for JSON output
125 * @throws ApexModelException on errors setting output type
127 public void setJsonOutput(final boolean jsonOutput) throws ApexModelException {
128 this.jsonOutput = jsonOutput;
130 // Set up output specific parameters
131 if (this.jsonOutput) {
133 marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
134 marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
135 } catch (final Exception e) {
136 throw new ApexModelException("JAXB error setting marshaller for JSON output", e);
140 marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_XML);
141 } catch (final Exception e) {
142 throw new ApexModelException("JAXB error setting marshaller for XML output", e);
148 * This method validates the Apex concept then writes it into a stream.
150 * @param concept the concept to write
151 * @param apexConceptStream the stream to write to
152 * @throws ApexModelException on validation or writing exceptions
154 public void write(final C concept, final OutputStream apexConceptStream) throws ApexModelException {
155 Assertions.argumentNotNull(concept, CONCEPT_MAY_NOT_BE_NULL);
156 Assertions.argumentNotNull(apexConceptStream, CONCEPT_STREAM_MAY_NOT_BE_NULL);
158 this.write(concept, new OutputStreamWriter(apexConceptStream));
162 * This method validates the Apex concept then writes it into a writer.
164 * @param concept the concept to write
165 * @param apexConceptWriter the writer to write to
166 * @throws ApexModelException on validation or writing exceptions
168 public void write(final C concept, final Writer apexConceptWriter) throws ApexModelException {
169 Assertions.argumentNotNull(concept, CONCEPT_MAY_NOT_BE_NULL);
170 Assertions.argumentNotNull(apexConceptWriter, CONCEPT_WRITER_MAY_NOT_BE_NULL);
172 // Check if we should validate the concept
174 // Validate the concept first
175 final AxValidationResult validationResult = concept.validate(new AxValidationResult());
176 if (!validationResult.isValid()) {
178 "Apex concept xml (" + concept.getKey().getId() + ") validation failed: " + validationResult
180 throw new ApexModelException(message);
185 writeJson(concept, apexConceptWriter);
187 writeXml(concept, apexConceptWriter);
192 * This method writes the Apex concept into a writer in XML format.
194 * @param concept the concept to write
195 * @param apexConceptWriter the writer to write to
196 * @throws ApexModelException on validation or writing exceptions
198 private void writeXml(final C concept, final Writer apexConceptWriter) throws ApexModelException {
199 Assertions.argumentNotNull(concept, CONCEPT_MAY_NOT_BE_NULL);
201 LOGGER.debug("writing Apex concept XML . . .");
204 // Write the concept into a DOM document, then transform to add CDATA fields and pretty
205 // print, then write out the result
206 final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
207 docBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
208 final Document document = docBuilderFactory.newDocumentBuilder().newDocument();
210 // Marshal the concept into the empty document.
211 marshaller.marshal(concept, document);
213 final Transformer domTransformer = getTransformer();
215 // Convert the cDataFieldSet into a space delimited string
216 domTransformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS,
217 cdataFieldSet.toString().replaceAll("[\\[\\]\\,]", " "));
218 domTransformer.transform(new DOMSource(document), new StreamResult(apexConceptWriter));
219 } catch (JAXBException | TransformerException | ParserConfigurationException e) {
220 throw new ApexModelException("Unable to marshal Apex concept to XML", e);
222 LOGGER.debug("wrote Apex concept XML");
225 private Transformer getTransformer() throws TransformerConfigurationException {
226 // Transform the DOM to the output stream
227 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
228 final Transformer domTransformer = transformerFactory.newTransformer();
232 domTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
233 // May fail if not using XALAN XSLT engine. But not in any way vital
234 domTransformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
235 } catch (final Exception ignore) {
236 LOGGER.trace("Unable to set indent property", ignore);
238 return domTransformer;
242 * This method writes the Apex concept into a writer in JSON format.
244 * @param concept the concept to write
245 * @param apexConceptWriter the writer to write to
246 * @throws ApexModelException on validation or writing exceptions
248 private void writeJson(final C concept, final Writer apexConceptWriter) throws ApexModelException {
249 Assertions.argumentNotNull(concept, CONCEPT_MAY_NOT_BE_NULL);
251 LOGGER.debug("writing Apex concept JSON . . .");
254 marshaller.marshal(concept, apexConceptWriter);
255 } catch (final JAXBException e) {
256 throw new ApexModelException("Unable to marshal Apex concept to JSON", e);
258 LOGGER.debug("wrote Apex concept JSON");
262 * Gets the validation flag value.
264 * @return the validation flag value
266 public boolean getValidateFlag() {
271 * Sets the validation flag.
273 * @param validateFlag the validation flag value
275 public void setValidateFlag(final boolean validateFlag) {
276 this.validateFlag = validateFlag;