All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.foursoft.xml.ExtendedUnmarshaller Maven / Gradle / Ivy

/*-
 * ========================LICENSE_START=================================
 * xml-runtime
 * %%
 * Copyright (C) 2019 4Soft GmbH
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * =========================LICENSE_END==================================
 */
package com.foursoft.xml;

import com.foursoft.xml.annotations.XmlBackReference;
import com.foursoft.xml.annotations.XmlParent;
import com.foursoft.xml.postprocessing.*;
import com.sun.xml.bind.v2.util.XmlFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.sax.SAXSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;

/**
 * Provides extended unmarshalling capabilities for a JAXB model. Capabilities
 * are:
 * 
    *
  • Initializing of parent associations and back references (see * {@link XmlParent} and {@link XmlBackReference}).
  • *
  • Creation of an {@link IdLookupProvider} to provide a fast id-based index * for the unmarshalled model.
  • *
  • Registration and execution of custom {@link ModelPostProcessor}.
  • *
* This is a wrapper arround an {@link Unmarshaller}. *

* The {@link ExtendedUnmarshaller} is required in order to correctly integrate * the two phase model post processing (see {@link ModelPostProcessor} into the * unmarshalling process. * * @param Type of the root element to unmarshall. * @param Type of elements that are identifiable. * @author becker */ public class ExtendedUnmarshaller { private final ModelPostProcessorRegistry postProcessorRegistry; private final List> idLookupGenerators = new ArrayList<>(); private final Unmarshaller unmarshaller; private final Class rootElement; /** * XMLReader that will be used to parse a document. */ private XMLReader reader = null; public ExtendedUnmarshaller(final Class rootElement) throws JAXBException { this.rootElement = rootElement; final String packageName = rootElement.getPackage().getName(); final JAXBContext context = JaxbContextFactory.initializeContext(packageName, this.getClass().getClassLoader()); postProcessorRegistry = new ModelPostProcessorRegistry(packageName); unmarshaller = context.createUnmarshaller(); } /** * Turns on the capability to initialize parent associations and * backreferences. * * @return Returns this for method chaining / fluent * initialization. */ public ExtendedUnmarshaller withBackReferences() { postProcessorRegistry.withFactory(ReflectiveAssociationPostProcessor::new); return this; } /** * Adds a custom {@link ModelPostProcessor} to the deserialization. * * @param modelPostProcessor the model post processor to use. Must not be null. * @return this for fluent API */ public ExtendedUnmarshaller withCustomPostProcessor(final ModelPostProcessor modelPostProcessor) { postProcessorRegistry.addDefaultPostProcessor(modelPostProcessor); return this; } /** * The jaxb unmarshaller has an event handler which is intercepted to provide the event for the consumer. * This will reset the handler first to the default handler, and then add the given consumer to it * * @param eventConsumer the consumer for the jaxb validation event * @return this for fluent API * @throws JAXBException if the unmarshalling goes wrong */ public ExtendedUnmarshaller withEventLogging(final Consumer eventConsumer) throws JAXBException { final ValidationEventHandler eventHandler = unmarshaller.getEventHandler(); if (eventHandler instanceof InterceptEventHandler) { ((InterceptEventHandler) eventHandler).setEventConsumer(eventConsumer); } else { unmarshaller.setEventHandler(new InterceptEventHandler(eventConsumer, eventHandler)); } return this; } /** * Defines an id mapper to create a {@link IdLookupProvider} during the * unmarshalling process. *

* If no id mapper is defined, the resulting {@link JaxbModel} will have no * {@link IdLookupProvider} initialized. *

* If more than one id mapper is registered, all are processed and the * result is merged into one {@link IdLookupProvider}. This allows a common * the handling of all classes during the unmarshalling, even if the do not * have a shared superclass or interface. * * @param classOfIdentifiableElements the class of the identifiable element * @param idMapper the mapper function to get the id of each element * @return this for fluent API */ public ExtendedUnmarshaller withIdMapper(final Class classOfIdentifiableElements, final Function idMapper) { final IdLookupGeneratorPostProcessor idLookupGenerator = new IdLookupGeneratorPostProcessor<>( classOfIdentifiableElements, idMapper); idLookupGenerators.add(idLookupGenerator); postProcessorRegistry.addDefaultPostProcessor(idLookupGenerator); return this; } public JaxbModel unmarshall(final InputStream resource) throws JAXBException { final ModelPostProcessorManager modelPostProcessorManager = new ModelPostProcessorManager( postProcessorRegistry); unmarshaller.setListener(modelPostProcessorManager); final R root = rootElement.cast(unmarshaller.unmarshal(new SAXSource(getXMLReader(),new InputSource(resource)))); modelPostProcessorManager.doPostProcessing(); final Optional> idLookupProvider = idLookupGenerators.stream() .map(IdLookupGeneratorPostProcessor::createIdLookkup) .reduce(IdLookupProvider::merge); cleanUp(); return new JaxbModel<>(root, idLookupProvider.orElse(null)); } /** * Provides access to the internal jax-b unmarshaller for further configuration. Use with caution. * * @return the internal jax-b unmarshaller */ public Unmarshaller getUnmarshaller() { return unmarshaller; } private void cleanUp() { // Allow garbage collection. unmarshaller.setListener(null); postProcessorRegistry.clearStateOfPostProcessors(); } public static class InterceptEventHandler implements ValidationEventHandler { private final ValidationEventHandler originalEventHandler; private Consumer eventConsumer; public InterceptEventHandler(final Consumer eventConsumer, final ValidationEventHandler originalEventHandler) { Objects.requireNonNull(originalEventHandler); Objects.requireNonNull(eventConsumer); this.originalEventHandler = originalEventHandler; this.eventConsumer = eventConsumer; } @Override public boolean handleEvent(final ValidationEvent event) { eventConsumer.accept(event); return originalEventHandler.handleEvent(event); } public void setEventConsumer(final Consumer eventConsumer) { Objects.requireNonNull(eventConsumer); this.eventConsumer = eventConsumer; } } /** * Obtains a configured XMLReader. * * {@link Unmarshaller} is not re-entrant, so we will * only use one instance of XMLReader. * * Overriden in order to fix potential security issue. * See javax.xml.bind.helpers.AbstractUnmarshallerImpl#getXMLReader() * */ protected XMLReader getXMLReader() throws JAXBException { if (reader == null) { try { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); factory.setValidating(false); SAXParser parser = factory.newSAXParser(); parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD,""); parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA,""); reader = parser.getXMLReader(); } catch (ParserConfigurationException|SAXException e) { throw new JAXBException(e); } } return reader; } }