8d6c01e4eb4cad5ba3a62f7a9c662a6b6be0ddaa
[policy/apex-pdp.git] / model / basic-model / src / main / java / org / onap / policy / apex / model / basicmodel / handling / ApexModelWriter.java
1 /*-
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
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.apex.model.basicmodel.handling;
23
24 import java.io.OutputStream;
25 import java.io.OutputStreamWriter;
26 import java.io.Writer;
27 import java.util.Set;
28 import java.util.TreeSet;
29
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;
43
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;
53
54 /**
55  * This class writes an Apex concept to an XML file or JSON file from a Java Apex Concept.
56  *
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)
59  */
60 public class ApexModelWriter<C extends AxConcept> {
61
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";
65
66     // Get a reference to the logger
67     private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexModelWriter.class);
68
69     // Writing as JSON or XML
70     private boolean jsonOutput = false;
71
72     // The list of fields to output as CDATA
73     private final Set<String> cdataFieldSet = new TreeSet<>();
74
75     // The Marshaller for the Apex concepts
76     private Marshaller marshaller = null;
77
78     // All written concepts are validated before writing if this flag is set
79     private boolean validateFlag = true;
80
81     /**
82      * Constructor, initiates the writer.
83      *
84      * @param rootConceptClass the root concept class for concept reading
85      * @throws ApexModelException the apex concept writer exception
86      */
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");
90
91         try {
92             final JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[]{rootConceptClass}, null);
93
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);
100         }
101     }
102
103     /**
104      * The set of fields to be output as CDATA.
105      *
106      * @return the set of fields
107      */
108     public Set<String> getCDataFieldSet() {
109         return cdataFieldSet;
110     }
111
112     /**
113      * Return true if JSON output enabled, XML output if false.
114      *
115      * @return true for JSON output
116      */
117     public boolean isJsonOutput() {
118         return jsonOutput;
119     }
120
121     /**
122      * Set the value of JSON output, true for JSON output, false for XML output.
123      *
124      * @param jsonOutput true for JSON output
125      * @throws ApexModelException on errors setting output type
126      */
127     public void setJsonOutput(final boolean jsonOutput) throws ApexModelException {
128         this.jsonOutput = jsonOutput;
129
130         // Set up output specific parameters
131         if (this.jsonOutput) {
132             try {
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);
137             }
138         } else {
139             try {
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);
143             }
144         }
145     }
146
147     /**
148      * This method validates the Apex concept then writes it into a stream.
149      *
150      * @param concept           the concept to write
151      * @param apexConceptStream the stream to write to
152      * @throws ApexModelException on validation or writing exceptions
153      */
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);
157
158         this.write(concept, new OutputStreamWriter(apexConceptStream));
159     }
160
161     /**
162      * This method validates the Apex concept then writes it into a writer.
163      *
164      * @param concept           the concept to write
165      * @param apexConceptWriter the writer to write to
166      * @throws ApexModelException on validation or writing exceptions
167      */
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);
171
172         // Check if we should validate the concept
173         if (validateFlag) {
174             // Validate the concept first
175             final AxValidationResult validationResult = concept.validate(new AxValidationResult());
176             if (!validationResult.isValid()) {
177                 String message =
178                     "Apex concept xml (" + concept.getKey().getId() + ") validation failed: " + validationResult
179                         .toString();
180                 throw new ApexModelException(message);
181             }
182         }
183
184         if (jsonOutput) {
185             writeJson(concept, apexConceptWriter);
186         } else {
187             writeXml(concept, apexConceptWriter);
188         }
189     }
190
191     /**
192      * This method writes the Apex concept into a writer in XML format.
193      *
194      * @param concept           the concept to write
195      * @param apexConceptWriter the writer to write to
196      * @throws ApexModelException on validation or writing exceptions
197      */
198     private void writeXml(final C concept, final Writer apexConceptWriter) throws ApexModelException {
199         Assertions.argumentNotNull(concept, CONCEPT_MAY_NOT_BE_NULL);
200
201         LOGGER.debug("writing Apex concept XML . . .");
202
203         try {
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();
209
210             // Marshal the concept into the empty document.
211             marshaller.marshal(concept, document);
212
213             final Transformer domTransformer = getTransformer();
214
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);
221         }
222         LOGGER.debug("wrote Apex concept XML");
223     }
224
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();
229
230         // Pretty print
231         try {
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);
237         }
238         return domTransformer;
239     }
240
241     /**
242      * This method writes the Apex concept into a writer in JSON format.
243      *
244      * @param concept           the concept to write
245      * @param apexConceptWriter the writer to write to
246      * @throws ApexModelException on validation or writing exceptions
247      */
248     private void writeJson(final C concept, final Writer apexConceptWriter) throws ApexModelException {
249         Assertions.argumentNotNull(concept, CONCEPT_MAY_NOT_BE_NULL);
250
251         LOGGER.debug("writing Apex concept JSON . . .");
252
253         try {
254             marshaller.marshal(concept, apexConceptWriter);
255         } catch (final JAXBException e) {
256             throw new ApexModelException("Unable to marshal Apex concept to JSON", e);
257         }
258         LOGGER.debug("wrote Apex concept JSON");
259     }
260
261     /**
262      * Gets the validation flag value.
263      *
264      * @return the validation flag value
265      */
266     public boolean getValidateFlag() {
267         return validateFlag;
268     }
269
270     /**
271      * Sets the validation flag.
272      *
273      * @param validateFlag the validation flag value
274      */
275     public void setValidateFlag(final boolean validateFlag) {
276         this.validateFlag = validateFlag;
277     }
278 }