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

org.opensaml.saml.metadata.resolver.impl.AbstractReloadingMetadataResolver Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the University Corporation for Advanced Internet Development,
 * Inc. (UCAID) under one or more contributor license agreements.  See the
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID 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.opensaml.saml.metadata.resolver.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Timer;
import java.util.TimerTask;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.io.UnmarshallingException;
import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver;
import org.opensaml.saml.metadata.resolver.filter.FilterException;
import org.opensaml.saml.saml2.common.SAML2Support;
import org.opensaml.saml.saml2.common.TimeBoundSAMLObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.TimerSupport;
import net.shibboleth.utilities.java.support.resolver.ResolverException;

/**
 * Base class for metadata providers that cache and periodically refresh their metadata.
 * 
 * This metadata provider periodically checks to see if the read metadata file has changed. The delay between each
 * refresh interval is calculated as follows. If no validUntil or cacheDuration is present then the
 * {@link #getMaxRefreshDelay()} value is used. Otherwise, the earliest refresh interval of the metadata file is checked
 * by looking for the earliest of all the validUntil attributes and cacheDuration attributes. If that refresh interval
 * is larger than the max refresh delay then {@link #getMaxRefreshDelay()} is used. If that number is smaller than the
 * min refresh delay then {@link #getMinRefreshDelay()} is used. Otherwise the calculated refresh delay multiplied by
 * {@link #getRefreshDelayFactor()} is used. By using this factor, the provider will attempt to be refresh before the
 * cache actually expires, allowing a some room for error and recovery. Assuming the factor is not exceedingly close to
 * 1.0 and a min refresh delay that is not overly large, this refresh will likely occur a few times before the cache
 * expires.
 */
public abstract class AbstractReloadingMetadataResolver extends AbstractBatchMetadataResolver 
        implements RefreshableMetadataResolver {

    /** Class logger. */
    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractReloadingMetadataResolver.class);

    /** Timer used to schedule background metadata update tasks. */
    private Timer taskTimer;
    
    /** Whether we created our own task timer during object construction. */
    private boolean createdOwnTaskTimer;
        
    /** Current task to refresh metadata. */
    private RefreshMetadataTask refreshMetadataTask;
    
    /** Factor used to compute when the next refresh interval will occur. Default value: 0.75 */
    private float refreshDelayFactor = 0.75f;

    /**
     * Refresh interval used when metadata does not contain any validUntil or cacheDuration information.
     * Default value: 4 hours.
     */
    @Nonnull private Duration maxRefreshDelay;

    /** Floor for the refresh interval. Default value: 5 minutes. */
    @Nonnull private Duration minRefreshDelay;

    /** Time when the currently cached metadata file expires. */
    @Nullable private Instant expirationTime;
    
    /** Impending expiration warning threshold for metadata refresh. Default value: 0 (disabled). */
    @Nonnull private Duration expirationWarningThreshold;

    /** Last time the metadata was updated. */
    @Nullable private Instant lastUpdate;

    /** Last time a refresh cycle occurred. */
    @Nullable private Instant lastRefresh;

    /** Next time a refresh cycle will occur. */
    @Nullable private Instant nextRefresh;
    
    /** Last time a successful refresh cycle occurred. */
    @Nullable private Instant lastSuccessfulRefresh;

    /** Flag indicating whether last refresh cycle was successful. */
    @Nullable private Boolean wasLastRefreshSuccess;
    
    /** Internal flag for tracking success during the refresh operation. */
    private boolean trackRefreshSuccess;
    
    /** Reason for the failure of the last refresh.  Will be null if last refresh was success. */
    @Nullable private Throwable lastFailureCause;


    /** Constructor. */
    protected AbstractReloadingMetadataResolver() {
        this(null);
    }

    /**
     * Constructor.
     * 
     * @param backgroundTaskTimer time used to schedule background refresh tasks
     */
    protected AbstractReloadingMetadataResolver(@Nullable final Timer backgroundTaskTimer) {
        setCacheSourceMetadata(true);
        
        minRefreshDelay = Duration.ofMinutes(5);
        maxRefreshDelay = Duration.ofHours(4);
        
        expirationWarningThreshold = Duration.ZERO;
        
        if (backgroundTaskTimer == null) {
            taskTimer = new Timer(TimerSupport.getTimerName(this), true);
            createdOwnTaskTimer = true;
        } else {
            taskTimer = backgroundTaskTimer;
        }
    }
    
    /** {@inheritDoc} */
    @Override
    protected void setCacheSourceMetadata(final boolean flag) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        if (!flag) {
            log.warn("{} Caching of source metadata may not be disabled for reloading metadata resolvers", 
                    getLogPrefix());
        } else {
            super.setCacheSourceMetadata(flag);
        }
    }

    /**
     * Gets the time when the currently cached metadata expires.
     * 
     * @return time when the currently cached metadata expires, or null if no metadata is cached
     */
    public Instant getExpirationTime() {
        return expirationTime;
    }

    /** {@inheritDoc} */
    @Override
    @Nullable public Instant getLastUpdate() {
        return lastUpdate;
    }

    /** {@inheritDoc} */
    @Override @Nullable public Instant getLastRefresh() {
        return lastRefresh;
    }
    
    /** {@inheritDoc} */
    @Nullable public Instant getLastSuccessfulRefresh() {
        return lastSuccessfulRefresh;
    }

    /** {@inheritDoc} */
    @Nullable public Boolean wasLastRefreshSuccess() {
        return wasLastRefreshSuccess;
    }

    /** {@inheritDoc} */
    @Nullable public Throwable getLastFailureCause() {
        return lastFailureCause;
    }

    /**
     * Gets the time when the next refresh cycle will occur.
     * 
     * @return time when the next refresh cycle will occur
     */
    public Instant getNextRefresh() {
        return nextRefresh;
    }

    /**
     * Gets the impending expiration warning threshold used at refresh time.
     * 
     * @return threshold for logging a warning if live metadata will soon expire
     */
    @Nonnull public Duration getExpirationWarningThreshold() {
        return expirationWarningThreshold;
    }

    /**
     * Sets the impending expiration warning threshold used at refresh time.
     * 
     * @param threshold the threshold for logging a warning if live metadata will soon expire
     */
    public void setExpirationWarningThreshold(@Nonnull final Duration threshold) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
        
        Constraint.isNotNull(threshold, "Expiration warning threshold cannot be null");
        Constraint.isFalse(threshold.isNegative(), "Expiration warning threshold cannot be negative");
        
        expirationWarningThreshold = threshold;
    }
    
    /**
     * Gets the maximum amount of time between refresh intervals.
     * 
     * @return maximum amount of time between refresh intervals
     */
    @Nonnull public Duration getMaxRefreshDelay() {
        return maxRefreshDelay;
    }

    /**
     * Sets the maximum amount of time between refresh intervals.
     * 
     * @param delay maximum amount of time between refresh intervals
     */
    public void setMaxRefreshDelay(@Nonnull final Duration delay) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
        
        Constraint.isNotNull(delay, "Maximum refresh delay cannot be null");
        Constraint.isFalse(delay.isNegative() || delay.isZero(), "Maximum refresh delay must be greater than 0");

        maxRefreshDelay = delay;
    }

    /**
     * Gets the delay factor used to compute the next refresh time.
     * 
     * @return delay factor used to compute the next refresh time
     */
    public float getRefreshDelayFactor() {
        return refreshDelayFactor;
    }

    /**
     * Sets the delay factor used to compute the next refresh time. The delay must be between 0.0 and 1.0, exclusive.
     * 
     * @param factor delay factor used to compute the next refresh time
     */
    public void setRefreshDelayFactor(final float factor) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        if (factor <= 0 || factor >= 1) {
            throw new IllegalArgumentException("Refresh delay factor must be a number between 0.0 and 1.0, exclusive");
        }

        refreshDelayFactor = factor;
    }

    /**
     * Gets the minimum amount of time between refreshes.
     * 
     * @return minimum amount of time between refreshes
     */
    @Nonnull public Duration getMinRefreshDelay() {
        return minRefreshDelay;
    }

    /**
     * Sets the minimum amount of time between refreshes.
     * 
     * @param delay minimum amount of time between refreshes
     */
    public void setMinRefreshDelay(@Nonnull final Duration delay) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        Constraint.isNotNull(delay, "Minimum refresh delay cannot be null");
        Constraint.isFalse(delay.isNegative() || delay.isZero(), "Minimum refresh delay must be greater than 0");

        minRefreshDelay = delay;
    }

    /** {@inheritDoc} */
    @Override
    protected void doDestroy() {
        refreshMetadataTask.cancel();
        
        if (createdOwnTaskTimer) {
            taskTimer.cancel();
        }
        
        expirationTime = null;
        lastRefresh = null;
        lastUpdate = null;
        nextRefresh = null;
        wasLastRefreshSuccess = null;
        lastSuccessfulRefresh = null;
        lastFailureCause = null;
        
        super.doDestroy();
    }

    /** {@inheritDoc} */
    @Override
    protected void initMetadataResolver() throws ComponentInitializationException {
        super.initMetadataResolver();
        
        try {
            refresh();
        } catch (final ResolverException e) {
            throw new ComponentInitializationException("Error refreshing metadata during init", e);
        }
        
        if (minRefreshDelay.compareTo(maxRefreshDelay) > 0) {
            throw new ComponentInitializationException("Minimum refresh delay " + minRefreshDelay
                    + " is greater than maximum refresh delay " + maxRefreshDelay);
        }
    }

    /**
     * Refreshes the metadata from its source.
     * 
     * @throws ResolverException thrown is there is a problem retrieving and processing the metadata
     */
    @Override
    public synchronized void refresh() throws ResolverException {
        Instant now = null;
        String mdId = null;
        trackRefreshSuccess = false;

        try {
            // In case a destroy() thread beat this thread into the monitor.
            if (isDestroyed()) {
                return;
            }

            // A manual refresh() must cancel the previously-scheduled future task, since will (re)schedule its own.
            // If this execution *is* the task, it's ok to cancel ourself, we're already running.
            if (refreshMetadataTask != null) {
                refreshMetadataTask.cancel();
            }
            
            now = Instant.now();
            mdId = getMetadataIdentifier();

            log.debug("{} Beginning refresh of metadata from '{}'", getLogPrefix(), mdId);
        
            final byte[] mdBytes = fetchMetadata();
            if (mdBytes == null) {
                log.info("{} Metadata from '{}' has not changed since last refresh", getLogPrefix(), mdId);
                processCachedMetadata(mdId, now);
            } else {
                log.debug("{} Processing new metadata from '{}'", getLogPrefix(), mdId);
                processNewMetadata(mdId, now, mdBytes);
            }
        } catch (final Throwable t) {
            trackRefreshSuccess = false;
            lastFailureCause = t;
            nextRefresh = Instant.now().plus(computeNextRefreshDelay(null));
            if (t instanceof Exception) {
                log.error("{} Error occurred while attempting to refresh metadata from '{}'", getLogPrefix(), mdId);
                throw new ResolverException("Exception during refresh", (Exception) t);
            }
            log.error("{} Error occurred while attempting to refresh metadata from '{}'", getLogPrefix(), mdId, t);
            throw new ResolverException(String.format("Saw an error of type '%s' with message '%s'", 
                    t.getClass().getName(), t.getMessage()));
        } finally {
            logCachedMetadataExpiration(now);
            
            if (trackRefreshSuccess) {
                wasLastRefreshSuccess = true;
                lastSuccessfulRefresh = now;
                lastFailureCause = null;
            } else {
                wasLastRefreshSuccess = false;
            }
            
            refreshMetadataTask = new RefreshMetadataTask();
            final long nextRefreshDelay = nextRefresh.toEpochMilli() - System.currentTimeMillis();
            taskTimer.schedule(refreshMetadataTask, nextRefreshDelay);
            log.info("{} Next refresh cycle for metadata provider '{}' will occur on '{}' ('{}' local time)",
                    new Object[] {getLogPrefix(), mdId, nextRefresh, 
                            nextRefresh.atZone(ZoneId.systemDefault()),});
            lastRefresh = now;
        }
    }

    /**
     * Check cached metadata for expiration or pending expiration and log appropriately.
     *
     * @param now the current date/time
     */
    private void logCachedMetadataExpiration(@Nonnull final Instant now) {
        final String mdId = getMetadataIdentifier();
        final XMLObject cached = getBackingStore().getCachedOriginalMetadata();
        if (cached != null && !isValid(cached)) {
            log.warn("{} Metadata root from '{}' currently live (post-refresh) is expired or otherwise invalid",
                    getLogPrefix(), mdId);
        } else if (cached instanceof TimeBoundSAMLObject) {
            final TimeBoundSAMLObject timebound = (TimeBoundSAMLObject) cached;
            if (isRequireValidMetadata() && timebound.getValidUntil() != null) {
                if (!getExpirationWarningThreshold().isZero() 
                        && timebound.getValidUntil().isBefore(now.plus(getExpirationWarningThreshold()))) {
                    log.warn("{} Metadata root from '{}' currently live (post-refresh) will expire "
                            + "within the configured threshhold at '{}'",
                            getLogPrefix(), mdId, timebound.getValidUntil());
                } else if (timebound.getValidUntil().isBefore(nextRefresh)) {
                    log.warn("{} Metadata root from '{}' currently live (post-refresh) will expire "
                            + "at '{}' before the next refresh scheduled for {}'",
                            getLogPrefix(), mdId, timebound.getValidUntil(), nextRefresh);
                }
            }
        }
    }

    /**
     * Gets an identifier which may be used to distinguish this metadata in logging statements.
     * 
     * @return identifier which may be used to distinguish this metadata in logging statements
     */
    protected abstract String getMetadataIdentifier();

    /**
     * Fetches metadata from a source.
     * 
     * @return the fetched metadata, or null if the metadata is known not to have changed since the last retrieval
     * 
     * @throws ResolverException thrown if there is a problem fetching the metadata
     */
    protected abstract byte[] fetchMetadata() throws ResolverException;

    /**
     * Unmarshalls the given metadata bytes.
     * 
     * @param metadataBytes raw metadata bytes
     * 
     * @return the metadata
     * 
     * @throws ResolverException thrown if the metadata can not be unmarshalled
     */
    protected XMLObject unmarshallMetadata(final byte[] metadataBytes) throws ResolverException {
        try {
            return unmarshallMetadata(new ByteArrayInputStream(metadataBytes));
        } catch (final UnmarshallingException e) {
            final String errorMsg = "Unable to unmarshall metadata";
            log.error("{} {}: {}", getLogPrefix(), errorMsg, e.getMessage());
            throw new ResolverException(errorMsg, e);
        }
    }

    /**
     * Processes a cached metadata document in order to determine, and schedule, the next time it should be refreshed.
     * 
     * @param metadataIdentifier identifier of the metadata source
     * @param refreshStart when the current refresh cycle started
     * 
     * @throws ResolverException throw is there is a problem process the cached metadata
     */
    protected void processCachedMetadata(final String metadataIdentifier, final Instant refreshStart)
            throws ResolverException {
        log.debug("{} Computing new expiration time for cached metadata from '{}'", getLogPrefix(), metadataIdentifier);
        final Instant metadataExpirationTime = 
                SAML2Support.getEarliestExpiration(getBackingStore().getCachedOriginalMetadata(),
                refreshStart.plus(getMaxRefreshDelay()), refreshStart);

        trackRefreshSuccess = true;
        expirationTime = metadataExpirationTime;
        nextRefresh = Instant.now().plus(computeNextRefreshDelay(expirationTime));
    }

    /**
     * Process a new metadata document. Processing include unmarshalling and filtering metadata, determining the next
     * time is should be refreshed and scheduling the next refresh cycle.
     * 
     * @param metadataIdentifier identifier of the metadata source
     * @param refreshStart when the current refresh cycle started
     * @param metadataBytes raw bytes of the new metadata document
     * 
     * @throws ResolverException thrown if there is a problem unmarshalling or filtering the new metadata
     */
    protected void processNewMetadata(final String metadataIdentifier, final Instant refreshStart,
            final byte[] metadataBytes) throws ResolverException {
        log.debug("{} Unmarshalling metadata from '{}'", getLogPrefix(), metadataIdentifier);
        final XMLObject metadata = unmarshallMetadata(metadataBytes);

        if (!isValid(metadata)) {
            processPreExpiredMetadata(metadataIdentifier, refreshStart, metadataBytes, metadata);
        } else {
            processNonExpiredMetadata(metadataIdentifier, refreshStart, metadataBytes, metadata);
        }
    }

    /**
     * Processes metadata that has been determined to be invalid (usually because it's already expired) at the time it
     * was fetched. A metadata document is considered be invalid if its root element returns false when passed to the
     * {@link #isValid(XMLObject)} method.
     * 
     * @param metadataIdentifier identifier of the metadata source
     * @param refreshStart when the current refresh cycle started
     * @param metadataBytes raw bytes of the new metadata document
     * @param metadata new metadata document unmarshalled
     */
    protected void processPreExpiredMetadata(final String metadataIdentifier, final Instant refreshStart,
            final byte[] metadataBytes, final XMLObject metadata) {
        log.warn("{} Entire metadata document from '{}' was expired at time of loading, " 
                + "previous metadata retained, if any", getLogPrefix(), metadataIdentifier);

        nextRefresh = Instant.now().plus(computeNextRefreshDelay(null));
        trackRefreshSuccess = false;
        // Note: We don't throw this b/c it would change behavior wrt to init and failFast,
        // but we still want to expose the failure cause to clients.
        lastFailureCause = new ResolverException("Entire metadata document was already expired at time of loading");
    }

    /**
     * Processes metadata that has been determined to be valid at the time it was fetched. A metadata document is
     * considered to be valid if its root element returns true when passed to the {@link #isValid(XMLObject)} method.
     * 
     * @param metadataIdentifier identifier of the metadata source
     * @param refreshStart when the current refresh cycle started
     * @param metadataBytes raw bytes of the new metadata document
     * @param metadata new metadata document unmarshalled
     * 
     * @throws ResolverException thrown if there s a problem processing the metadata
     */
    protected void processNonExpiredMetadata(final String metadataIdentifier, final Instant refreshStart,
            final byte[] metadataBytes, final XMLObject metadata) throws ResolverException {
        final Document metadataDom = metadata.getDOM().getOwnerDocument();

        log.debug("{} Preprocessing metadata from '{}'", getLogPrefix(), metadataIdentifier);
        BatchEntityBackingStore newBackingStore = null;
        try {
            newBackingStore = preProcessNewMetadata(metadata);
        } catch (final FilterException e) {
            final String errMsg = "Error filtering metadata from " + metadataIdentifier;
            log.error("{} {}: {}", getLogPrefix(), errMsg, e.getMessage());
            throw new ResolverException(errMsg, e);
        }

        log.debug("{} Releasing cached DOM for metadata from '{}'", getLogPrefix(), metadataIdentifier);
        releaseMetadataDOM(newBackingStore.getCachedOriginalMetadata());
        releaseMetadataDOM(newBackingStore.getCachedFilteredMetadata());

        log.debug("{} Post-processing metadata from '{}'", getLogPrefix(), metadataIdentifier);
        postProcessMetadata(metadataBytes, metadataDom, newBackingStore.getCachedOriginalMetadata(), 
                newBackingStore.getCachedFilteredMetadata());

        log.debug("{} Computing expiration time for metadata from '{}'", getLogPrefix(), metadataIdentifier);
        // Note: As noted in its Javadocs, technically this method can sometimes return null, but won't in this case
        // since the candidate time (2nd arg) is not null.
        final Instant metadataExpirationTime = SAML2Support.getEarliestExpiration(
                newBackingStore.getCachedOriginalMetadata(), refreshStart.plus(getMaxRefreshDelay()),
                refreshStart);
        log.debug("{} Expiration of metadata from '{}' will occur at {}", getLogPrefix(), metadataIdentifier, 
                metadataExpirationTime.toString());

        // This is where the new processed data becomes effective. Exceptions thrown prior to this point
        // therefore result in the old data being kept effective.
        setBackingStore(newBackingStore);
        
        lastUpdate = refreshStart;
        trackRefreshSuccess = true;
        
        final Instant now = Instant.now();
        
        final Duration nextRefreshDelay;
        if (metadataExpirationTime.isBefore(now)) {
            expirationTime = now.plus(getMinRefreshDelay());
            nextRefreshDelay = getMaxRefreshDelay();
        } else {
            expirationTime = metadataExpirationTime;
            nextRefreshDelay = computeNextRefreshDelay(expirationTime);
        }
        nextRefresh = now.plus(nextRefreshDelay);

        log.info("{} New metadata successfully loaded for '{}'", getLogPrefix(), getMetadataIdentifier());
    }

    /**
     * Post-processing hook called after new metadata has been unmarshalled, filtered, and the DOM released (from the
     * {@link XMLObject}) but before the metadata is saved off. Any exception thrown by this hook will cause the
     * retrieved metadata to be discarded.
     * 
     * The default implementation of this method is a no-op
     * 
     * @param metadataBytes original raw metadata bytes retrieved via {@link #fetchMetadata}
     * @param metadataDom original metadata after it has been parsed in to a DOM document
     * @param originalMetadata original metadata prior to being filtered, with its DOM released
     * @param filteredMetadata metadata after it has been run through all registered filters and its DOM released
     * 
     * @throws ResolverException thrown if there is a problem with the provided data
     */
    protected void postProcessMetadata(final byte[] metadataBytes, final Document metadataDom,
            final XMLObject originalMetadata, final XMLObject filteredMetadata) throws ResolverException {

    }

    /**
     * Computes the delay until the next refresh time based on the current metadata's expiration time and the refresh
     * interval floor.
     * 
     * @param expectedExpiration the time when the metadata is expected to expire and need refreshing
     * 
     * @return delay until the next refresh time
     */
    @Nonnull protected Duration computeNextRefreshDelay(final Instant expectedExpiration) {
        final long now = System.currentTimeMillis();

        long expireInstant = 0;
        if (expectedExpiration != null) {
            expireInstant = expectedExpiration.toEpochMilli();
        }
        long refreshDelay = (long) ((expireInstant - now) * getRefreshDelayFactor());

        // if the expiration time was null or the calculated refresh delay was less than the floor
        // use the floor
        if (refreshDelay < getMinRefreshDelay().toMillis()) {
            refreshDelay = getMinRefreshDelay().toMillis();
        }

        return Duration.ofMillis(refreshDelay);
    }

    /**
     * Converts an InputStream into a byte array.
     * 
     * @param ins input stream to convert. The stream will be closed after its data is consumed.
     * 
     * @return resultant byte array
     * 
     * @throws ResolverException thrown if there is a problem reading the resultant byte array
     */
    protected byte[] inputstreamToByteArray(final InputStream ins) throws ResolverException {
        try {
            // 1 MB read buffer
            final byte[] buffer = new byte[1024 * 1024];
            final ByteArrayOutputStream output = new ByteArrayOutputStream();

            int n = 0;
            while (-1 != (n = ins.read(buffer))) {
                output.write(buffer, 0, n);
            }

            return output.toByteArray();
        } catch (final IOException e) {
            throw new ResolverException(e);
        } finally {
            try {
                ins.close();
            } catch (final IOException e) {
                // Ignore here.  If the read() threw also, then that should be reported, not this.
                // If the close() throws, we don't care b/c we've already read the bytes.
            }
        }

    }

    /** Background task that refreshes metadata. */
    private class RefreshMetadataTask extends TimerTask {

        /** {@inheritDoc} */
        //CheckStyle: ReturnCount OFF
        @Override public void run() {
            try {
                if (isDestroyed()) {
                    // just in case the metadata provider was destroyed before this task runs
                    return;
                }
                
                refresh();
            } catch (final ResolverException e) {
                // nothing to do, error message already logged by refreshMetadata()
                return;
            }
        }
        //CheckStyle: ReturnCount ON
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy