org.codice.ddf.parser.xml.XmlParser.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.parser.xml.XmlParser.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p>
 * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 * <p>
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 **/
package org.codice.ddf.parser.xml;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.codice.ddf.parser.Parser;
import org.codice.ddf.parser.ParserConfigurator;
import org.codice.ddf.parser.ParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * XML Parser implementation, uses JAXB internally to marshal and unmarshal object.
 */
public class XmlParser implements Parser {
    private static final Joiner CTX_JOINER = Joiner.on(":").skipNulls();

    private static final Logger LOGGER = LoggerFactory.getLogger(XmlParser.class);

    private LoadingCache<CacheKey, JAXBContext> jaxbContextCache = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.DAYS).maximumSize(1000).build(new CacheLoader<CacheKey, JAXBContext>() {
                @Override
                public JAXBContext load(CacheKey cacheKey) throws Exception {
                    JAXBContext jaxbContext;

                    try {
                        jaxbContext = JAXBContext.newInstance(cacheKey.joinedPath, cacheKey.loader);
                    } catch (JAXBException e) {
                        LOGGER.error("Unable to create JAXB context using context path: {}", cacheKey.joinedPath,
                                e);
                        throw e;
                    }
                    return jaxbContext;
                }
            });

    @Override
    public ParserConfigurator configureParser(List<String> contextPath, ClassLoader loader) {
        return new XmlParserConfigurator().setContextPath(contextPath).setClassLoader(loader);
    }

    @Override
    public void marshal(ParserConfigurator configurator, Object obj, OutputStream os) throws ParserException {
        marshal(configurator, marshaller -> {
            try {
                marshaller.marshal(obj, os);
            } catch (JAXBException e) {
                throw new RuntimeException("Error marshalling", e);
            }
        });
    }

    @Override
    public void marshal(ParserConfigurator configurator, Object obj, Node node) throws ParserException {
        marshal(configurator, marshaller -> {
            try {
                marshaller.marshal(obj, node);
            } catch (JAXBException e) {
                throw new RuntimeException("Error marshalling", e);
            }
        });
    }

    @Override
    public <T> T unmarshal(ParserConfigurator configurator, Class<? extends T> cls, final InputStream stream)
            throws ParserException {
        return unmarshal(configurator, unmarshaller -> {
            try {
                XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(stream);

                @SuppressWarnings("unchecked")
                T unmarshal = (T) unmarshaller.unmarshal(xmlStreamReader);
                return unmarshal;
            } catch (XMLStreamException | JAXBException e) {
                throw new RuntimeException("Error unmarshalling", e);
            }
        });
    }

    @Override
    public <T> T unmarshal(ParserConfigurator configurator, Class<? extends T> cls, final Node node)
            throws ParserException {
        return unmarshal(configurator, unmarshaller -> {
            try {
                @SuppressWarnings("unchecked")
                T unmarshal = (T) unmarshaller.unmarshal(node);
                return unmarshal;
            } catch (JAXBException e) {
                throw new RuntimeException("Error unmarshalling", e);
            }
        });
    }

    @Override
    public <T> T unmarshal(ParserConfigurator configurator, Class<? extends T> cls, final Source source)
            throws ParserException {
        return unmarshal(configurator, unmarshaller -> {
            try {
                @SuppressWarnings("unchecked")
                T unmarshal = (T) unmarshaller.unmarshal(source);
                return unmarshal;
            } catch (JAXBException e) {
                throw new RuntimeException("Error unmarshalling", e);
            }
        });
    }

    private void marshal(ParserConfigurator configurator, Consumer<Marshaller> marshallerConsumer)
            throws ParserException {
        JAXBContext jaxbContext = getContext(configurator.getContextPath(), configurator.getClassLoader());

        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(configurator.getClassLoader());
            Marshaller marshaller = jaxbContext.createMarshaller();
            if (configurator.getAdapter() != null) {
                marshaller.setAdapter(configurator.getAdapter());
            }
            if (configurator.getHandler() != null) {
                marshaller.setEventHandler(configurator.getHandler());
            }
            for (Map.Entry<String, Object> propRow : configurator.getProperties().entrySet()) {
                marshaller.setProperty(propRow.getKey(), propRow.getValue());
            }

            marshallerConsumer.accept(marshaller);
        } catch (RuntimeException e) {
            LOGGER.error("Error marshalling ", e);
            throw new ParserException("Error marshalling ", e);
        } catch (JAXBException e) {
            LOGGER.error("Error marshalling ", e);
            throw new ParserException("Error marshalling", e);
        } finally {
            Thread.currentThread().setContextClassLoader(tccl);
        }
    }

    private <T> T unmarshal(ParserConfigurator configurator, Function<Unmarshaller, T> func)
            throws ParserException {
        JAXBContext jaxbContext = getContext(configurator.getContextPath(), configurator.getClassLoader());

        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(configurator.getClassLoader());

            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            if (configurator.getAdapter() != null) {
                unmarshaller.setAdapter(configurator.getAdapter());
            }
            if (configurator.getHandler() != null) {
                unmarshaller.setEventHandler(configurator.getHandler());
            }
            for (Map.Entry<String, Object> propRow : configurator.getProperties().entrySet()) {
                unmarshaller.setProperty(propRow.getKey(), propRow.getValue());
            }

            return func.apply(unmarshaller);
        } catch (RuntimeException e) {
            LOGGER.error("Error unmarshalling ", e);
            throw new ParserException("Error unmarshalling", e);
        } catch (JAXBException e) {
            LOGGER.error("Error unmarshalling ", e);
            throw new ParserException("Error unmarshalling", e);
        } finally {
            Thread.currentThread().setContextClassLoader(tccl);
        }
    }

    private JAXBContext getContext(List<String> contextPath, ClassLoader loader) throws ParserException {
        String joinedPath = CTX_JOINER.join(contextPath);

        JAXBContext jaxbContext;

        try {
            jaxbContext = jaxbContextCache.get(new CacheKey(joinedPath, loader));
        } catch (ExecutionException e) {
            LOGGER.error("Unable to create JAXB context using context path: {}", joinedPath, e);
            throw new ParserException("Unable to create XmlParser", e.getCause());
        }

        return jaxbContext;
    }

    static class CacheKey {
        private final String joinedPath;

        private final ClassLoader loader;

        CacheKey(String joinedPath, ClassLoader loader) {
            this.joinedPath = joinedPath;
            this.loader = loader;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            CacheKey cacheKey = (CacheKey) o;

            return new EqualsBuilder().append(joinedPath, cacheKey.joinedPath).append(loader, cacheKey.loader)
                    .isEquals();
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(17, 37).append(joinedPath).append(loader).toHashCode();
        }
    }
}