org.jasig.services.persondir.support.xml.CachingJaxbLoaderImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.services.persondir.support.xml.CachingJaxbLoaderImpl.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig 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 org.jasig.services.persondir.support.xml;

import java.io.IOException;
import java.io.InputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

/**
 * Base logic for loading unmarshalling an XML document via JAXB and only reloading the cached object model when needed.
 * The class attempts to monitor the lastModified date of the {@link Resource} to determine when to reload. If that fails
 * the resource is reloaded periodically as specified by the {@link #setNoLastModifiedReloadPeriod(long)} property.
 * 
 * The class determines the return type and the base package to use for the {@link JAXBContext#newInstance(String)} call
 * via the loadedType parameter provided to the constructor.
 * 
 * @author Eric Dalquist
 * @version $Revision$
 */
public class CachingJaxbLoaderImpl<T> implements CachingJaxbLoader<T> {
    protected final Class<T> loadedType;

    protected long noLastModifiedReloadPeriod = 5 * 60 * 1000; //5 minute default
    protected Resource mappedXmlResource;

    protected T unmarshalledObject;
    protected long lastModifiedTime = Integer.MIN_VALUE;

    public CachingJaxbLoaderImpl(Class<T> loadedType) {
        Assert.notNull(loadedType, "loadedType can not be null");
        this.loadedType = loadedType;
    }

    public long getNoLastModifiedReloadPeriod() {
        return noLastModifiedReloadPeriod;
    }

    /**
     * Period between reloads if last-modified of the {@link Resource} cannot be determined
     */
    public void setNoLastModifiedReloadPeriod(long noLastModifiedReloadPeriod) {
        this.noLastModifiedReloadPeriod = noLastModifiedReloadPeriod;
    }

    public Resource getMappedXmlResource() {
        return mappedXmlResource;
    }

    /**
     * The XML resource to load.
     */
    public void setMappedXmlResource(Resource mappedXmlResource) {
        this.mappedXmlResource = mappedXmlResource;
    }

    /* (non-Javadoc)
     * @see org.jasig.services.persondir.support.xml.CachingJaxbLoader#getUnmarshalledObject()
     */
    public T getUnmarshalledObject() {
        return this.getUnmarshalledObject(null);
    }

    /* (non-Javadoc)
     * @see org.jasig.services.persondir.support.xml.CachingJaxbLoader#getUnmarshalledObject(org.jasig.services.persondir.support.xml.CachingJaxbLoader.UnmarshallingCallback)
     */
    public T getUnmarshalledObject(UnmarshallingCallback<T> callback) {
        //Only bother checking for a change if the object already exists
        Long lastModified = null;
        if (this.unmarshalledObject != null) {
            lastModified = this.getLastModified();

            //Return immediately if nothing has changed
            if (this.isCacheValid(lastModified)) {
                return this.unmarshalledObject;
            }
        }

        final InputStream xmlInputStream = this.getXmlInputStream();
        final JAXBContext jaxbContext = this.getJAXBContext();
        final Unmarshaller unmarshaller = this.getUnmarshaller(jaxbContext);
        final T unmarshalledObject = this.unmarshal(xmlInputStream, unmarshaller);

        if (callback != null) {
            callback.postProcessUnmarshalling(unmarshalledObject);
        }

        this.unmarshalledObject = unmarshalledObject;
        if (lastModified != null) {
            this.lastModifiedTime = lastModified;
        } else {
            this.lastModifiedTime = System.currentTimeMillis();
        }

        return this.unmarshalledObject;
    }

    /**
     * @return The last modified date for the XML file, null if it cannot be determined
     */
    protected Long getLastModified() {
        try {
            return this.mappedXmlResource.lastModified();
        } catch (IOException ioe) {
            return null;
        }
    }

    /**
     * Determines if the cached unmarshalled object is still valid
     * 
     * @param lastModified last modified timestamp of the resource, null if not known.
     * @return true if the cached object should be used
     */
    protected boolean isCacheValid(Long lastModified) {
        return (lastModified != null && lastModified <= this.lastModifiedTime) || (lastModified == null
                && (this.lastModifiedTime + this.noLastModifiedReloadPeriod) <= System.currentTimeMillis());
    }

    /**
     * @return The InputStream to read the XML file from
     */
    protected InputStream getXmlInputStream() {
        final InputStream xmlInputStream;
        try {
            xmlInputStream = this.mappedXmlResource.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException("Failed to open InputStream for Resource: " + this.mappedXmlResource, e);
        }
        return xmlInputStream;
    }

    /**
     * @return The JAXB context to parse the XML resource with
     */
    protected JAXBContext getJAXBContext() {
        final Package loadedPackage = this.loadedType.getPackage();
        final String filterDisplayPackage = loadedPackage.getName();
        try {
            return JAXBContext.newInstance(filterDisplayPackage);
        } catch (JAXBException e) {
            throw new RuntimeException("Failed to create " + JAXBContext.class + " to unmarshal " + this.loadedType,
                    e);
        }
    }

    /**
     * @param jaxbContext The context to get an unmarshaller for
     * @return An unmarshaller to use to generate object from the XML
     */
    protected Unmarshaller getUnmarshaller(final JAXBContext jaxbContext) {
        try {
            return jaxbContext.createUnmarshaller();
        } catch (JAXBException e) {
            throw new RuntimeException(
                    "Failed to create " + Unmarshaller.class + " to unmarshal " + this.loadedType, e);
        }
    }

    /**
     * @param xmlInputStream InputStream to read the XML from
     * @param unmarshaller Unmarshaller to generate the object from the XML with
     * @return An unmarshalled object model of the XML
     */
    @SuppressWarnings("unchecked")
    protected T unmarshal(final InputStream xmlInputStream, final Unmarshaller unmarshaller) {
        try {
            return (T) unmarshaller.unmarshal(xmlInputStream);
        } catch (JAXBException e) {
            throw new RuntimeException("Unexpected JAXB error while unmarshalling  " + this.mappedXmlResource, e);
        }
    }
}