/* * Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under * one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. KoSIT licenses this file * to you 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 de.kosit.validationtool.impl; import java.io.IOException; import java.io.StringWriter; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.StringJoiner; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBIntrospector; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.annotation.XmlRegistry; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import org.apache.commons.lang3.StringUtils; import lombok.extern.slf4j.Slf4j; /** * JAXB Conversion Utility. */ @Slf4j public class ConversionService { /** * Exception while serializing/deserializing with jaxb. */ public class ConversionExeption extends RuntimeException { /** * Constructor. * * @param message the message. * @param cause the cause */ public ConversionExeption(final String message, final Exception cause) { super(message, cause); } /** * Constructor. * * @param message the message. */ public ConversionExeption(final String message) { super(message); } } private static final int MAX_LOG_CONTENT = 50; // context setup private JAXBContext jaxbContext; public JAXBContext getJaxbContext() { if (this.jaxbContext == null) { initialize(); } return this.jaxbContext; } private static <T> QName createQName(final T model) { return new QName(model.getClass().getSimpleName().toLowerCase()); } private void checkInputEmpty(final URI xml) { if (xml == null) { throw new ConversionExeption("Can not unmarshal from empty input"); } } private <T> void checkTypeEmpty(final Class<T> type) { if (type == null) { throw new ConversionExeption("Can not unmarshal without type information. Need to specify a target type"); } } /** * Initialisiert den default context; Alle Packages mit {@link XmlRegistry XmlRegistries}. */ public void initialize() { final Collection<Package> p = new ArrayList<>(); p.add(de.kosit.validationtool.model.reportInput.ObjectFactory.class.getPackage()); p.add(de.kosit.validationtool.model.scenarios.ObjectFactory.class.getPackage()); initialize(p); } public void initialize(final Package... context) { initialize(Arrays.asList(context)); } /** * Initialisiert den conversion service mit den angegegebenen Packages. * * @param context packages für den JAXB Kontext */ public void initialize(final Collection<Package> context) { final String[] packages = context != null ? context.stream().map(Package::getName).toArray(String[]::new) : new String[0]; final StringJoiner joiner = new StringJoiner(":"); Arrays.stream(packages).forEach(p -> joiner.add(p)); initialize(joiner.toString()); } /** * Initialsiert den conversion service mit dem angegebenen Kontextpfad * * @param contextPath der Kontextpfad */ private void initialize(final String contextPath) { try { this.jaxbContext = JAXBContext.newInstance(contextPath); } catch (final JAXBException e) { throw new IllegalStateException(String.format("Can not create JAXB context for given context: %s", contextPath), e); } } /** * Unmarshalls a specifc xml model into a defined java object. * * @param xml the xml * @param type the expected type created * @param <T> type information * @return the created object */ public <T> T readXml(final URI xml, final Class<T> type) { return readXml(xml, type, null, null); } public <T> T readXml(final URI xml, final Class<T> type, final Schema schema) { return readXml(xml, type, schema, null); } public <T> T readXml(final URI xml, final Class<T> type, final Schema schema, final ValidationEventHandler handler) { checkInputEmpty(xml); checkTypeEmpty(type); CollectingErrorEventHandler defaultHandler = null; ValidationEventHandler handler2Use = handler; if (schema != null && handler == null) { defaultHandler = new CollectingErrorEventHandler(); handler2Use = defaultHandler; } try { final XMLInputFactory inputFactory = XMLInputFactory.newFactory(); inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); final XMLStreamReader xsr = inputFactory.createXMLStreamReader(new StreamSource(xml.toASCIIString())); final Unmarshaller u = getJaxbContext().createUnmarshaller(); u.setSchema(schema); u.setEventHandler(handler2Use); final T value = u.unmarshal(xsr, type).getValue(); if (defaultHandler != null && defaultHandler.hasErrors()) { throw new ConversionExeption( String.format("Schema errors while reading content from %s: %s", xml, defaultHandler.getErrorDescription())); } return value; } catch (final JAXBException | XMLStreamException e) { throw new ConversionExeption(String.format("Can not unmarshal to type %s from %s", type.getSimpleName(), xml.toString()), e); } } /** * Serializing an object to xml. * * @param model the object * @param <T> type of the object * @return the serialized form. */ public <T> String writeXml(final T model) { return writeXml(model, null, null); } public <T> String writeXml(final T model, final Schema schema, final ValidationEventHandler handler) { if (model == null) { throw new ConversionExeption("Can not serialize null"); } try ( final StringWriter w = new StringWriter() ) { final JAXBIntrospector introspector = getJaxbContext().createJAXBIntrospector(); final Marshaller marshaller = getJaxbContext().createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setSchema(schema); marshaller.setEventHandler(handler); final XMLOutputFactory xof = XMLOutputFactory.newFactory(); final XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(w); if (null == introspector.getElementName(model)) { final JAXBElement jaxbElement = new JAXBElement(createQName(model), model.getClass(), model); marshaller.marshal(jaxbElement, xmlStreamWriter); } else { marshaller.marshal(model, xmlStreamWriter); } xmlStreamWriter.flush(); return w.toString(); } catch (final JAXBException | IOException | XMLStreamException e) { throw new ConversionExeption(String.format("Error serializing Object %s", model.getClass().getName()), e); } } public <T> T readDocument(final Source source, final Class<T> type) { try { final Unmarshaller u = getJaxbContext().createUnmarshaller(); return u.unmarshal(source, type).getValue(); } catch (final JAXBException e) { throw new ConversionExeption(String.format("Can not unmarshal to type %s: %s", type.getSimpleName(), StringUtils.abbreviate(source.getSystemId(), MAX_LOG_CONTENT)), e); } } }