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

org.javamoney.moneta.internal.loader.LoadableResource Maven / Gradle / Ivy

Go to download

JSR 354 provides an API for representing, transporting, and performing comprehensive calculations with Money and Currency. This module implements JSR 354.

There is a newer version: 1.4.1
Show newest version
/*
 * Copyright (c) 2012, 2014, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag.
 *
 * Licensed 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.javamoney.moneta.internal.loader;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.javamoney.moneta.spi.LoadDataInformation;
import org.javamoney.moneta.spi.LoaderService;

/**
 * This class represent a resource that automatically is reloaded, if needed.
 * To create this instance use: {@link LoadableResourceBuilder}
 * @author Anatole Tresch
 */
public class LoadableResource {

    /**
     * The logger used.
     */
    private static final Logger LOG = Logger.getLogger(LoadableResource.class.getName());
    /**
     * Lock for this instance.
     */
    private final Object lock = new Object();
    /**
     * resource id.
     */
    private final String resourceId;
    /**
     * The remote URLs to be looked up (first wins).
     */
    private final List remoteResources = new ArrayList<>();
    /**
     * The fallback location (classpath).
     */
    private final URI fallbackLocation;
    /**
     * The cache used.
     */
    private final ResourceCache cache;
    /**
     * How many times this resource was successfully loaded.
     */
    private final AtomicInteger loadCount = new AtomicInteger();
    /**
     * How many times this resource was accessed.
     */
    private final AtomicInteger accessCount = new AtomicInteger();
    /**
     * The current data array.
     */
    private volatile SoftReference data;
    /**
     * THe timestamp of the last successful load.
     */
    private long lastLoaded;
    /**
     * The time to live (TTL) of cache entries in milliseconds, by default 24 h.
     */
    private long cacheTTLMillis = 3600000L * 24; // 24 h

    /**
     * The required update policy for this resource.
     */
    private final LoaderService.UpdatePolicy updatePolicy;
    /**
     * The resource configuration.
     */
    private final Map properties;


    LoadableResource(ResourceCache cache, LoadDataInformation loadDataInformation) {


        Objects.requireNonNull(loadDataInformation.getResourceId(), "resourceId required");
        Objects.requireNonNull(loadDataInformation.getProperties(), "properties required");
        Objects.requireNonNull(loadDataInformation.getUpdatePolicy(), "updatePolicy required");
        String val = loadDataInformation.getProperties().get("cacheTTLMillis");
        if (val != null) {
            this.cacheTTLMillis = Long.parseLong(val);
        }
        this.cache = cache;
        this.resourceId = loadDataInformation.getResourceId();
        this.updatePolicy = loadDataInformation.getUpdatePolicy();
        this.properties = loadDataInformation.getProperties();
        this.fallbackLocation = loadDataInformation.getBackupResource();
        this.remoteResources.addAll(Arrays.asList(loadDataInformation.getResourceLocations()));
    }

    /**
     * Get the UpdatePolicy of this resource.
     *
     * @return the UpdatePolicy of this resource, never null.
     */
    public LoaderService.UpdatePolicy getUpdatePolicy() {
        return updatePolicy;
    }

    /**
     * Get the configuration properties of this resource.
     *
     * @return the  configuration properties of this resource, never null.
     */
    public Map getProperties() {
        return properties;
    }

    /**
     * Loads the resource, first from the remote resources, if that fails from
     * the fallback location.
     *
     * @return true, if load succeeded.
     */
    public boolean load() {
        if ((lastLoaded + cacheTTLMillis) <= System.currentTimeMillis()) {
            clearCache();
        }
        return readCache() || !shouldReadDataFromFallback() || loadFallback();
    }

    private boolean shouldReadDataFromFallback() {
        return LoaderService.UpdatePolicy.NEVER.equals(updatePolicy) || !loadRemote();
    }

    /**
     * Get the resourceId.
     *
     * @return the resourceId
     */
    public final String getResourceId() {
        return resourceId;
    }

    /**
     * Get the remote locations.
     *
     * @return the remote locations, maybe empty.
     */
    public final List getRemoteResources() {
        return Collections.unmodifiableList(remoteResources);
    }

    /**
     * Return the fallback location.
     *
     * @return the fallback location, or null.
     */
    public final URI getFallbackResource() {
        return fallbackLocation;
    }

    /**
     * Get the number of active loads of this resource (InputStream).
     *
     * @return the number of successful loads.
     */
    public final int getLoadCount() {
        return loadCount.get();
    }

    /**
     * Get the number of successful accesses.
     *
     * @return the number of successful accesses.
     */
    public final int getAccessCount() {
        return accessCount.get();
    }

    /**
     * Get the resource data as input stream.
     *
     * @return the input stream.
     */
    public InputStream getDataStream() {
        return new WrappedInputStream(new ByteArrayInputStream(getData()));
    }

    /**
     * Get the timestamp of the last succesful load.
     *
     * @return the lastLoaded
     */
    public final long getLastLoaded() {
        return lastLoaded;
    }

    /**
     * Try to load the resource from the remote locations.
     *
     * @return true, on success.
     */
    public boolean loadRemote() {
        for (URI itemToLoad : remoteResources) {
            try {
                return load(itemToLoad, false);
            } catch (Exception e) {
                LOG.log(Level.INFO, "Failed to load resource: " + itemToLoad, e);
            }
        }
        return false;
    }

    /**
     * Try to load the resource from the fallback resources. This will override
     * any remote data already loaded, and also will clear the cached data.
     *
     * @return true, on success.
     */
    public boolean loadFallback() {
        try {
            if (fallbackLocation == null) {
                Logger.getLogger(getClass().getName()).warning("No fallback resource for " + this +
                        ", loadFallback not supported.");
                return false;
            }
            load(fallbackLocation, true);
            clearCache();
            return true;
        } catch (Exception e) {
            LOG.log(Level.SEVERE, "Failed to load fallback resource: " + fallbackLocation, e);
        }
        return false;
    }

    /**
     * This method is called when the cached data should be removed, e.g. after an explicit fallback reload, or
     * a clear operation.
     */
    protected void clearCache() {
        if (this.cache != null) {
            this.cache.clear(resourceId);
        }
    }

    /**
     * This method is called when the data should be loaded from the cache. This method abstracts the effective
     * caching mechanism implemented. By default it tries to read a file from the current user's home directory.
     * If the data could be read, #setData(byte[]) should be called to apply the data read.
     *
     * @return true, if data could be read and applied from the cache successfully.
     */
    protected boolean readCache() {
        if (this.cache != null && this.cache.isCached(resourceId)) {
            byte[] cachedData = this.cache.read(resourceId);
            if (cachedData != null) {
                setData(cachedData);
                return true;
            }
        }
        return false;
    }

    /**
     * This method is called after data could be successfully loaded from a non fallback resource. This method by
     * default writes an file containing the data into the user's local home directory, so subsequent or later calls,
     * even after a VM restart, should be able to recover this information.
     */
    protected void writeCache() throws IOException {
        if (this.cache != null) {
            byte[] cachedData = this.data == null ? null : this.data.get();
            if (cachedData == null) {
                return;
            }
            this.cache.write(resourceId, cachedData);
        }
    }

    /**
     * Tries to load the data from the given location. The location hereby can be a remote location or a local
     * location. Also it can be an URL pointing to a current dataset, or an url directing to fallback resources,
     * e.g. within the cuzrrent classpath.
     *
     * @param itemToLoad   the target {@link URL}
     * @param fallbackLoad true, for a fallback URL.
     */
    protected boolean load(URI itemToLoad, boolean fallbackLoad) {
        InputStream is = null;
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            URLConnection conn;

            String proxyPort = this.properties.get("proxy.port");
            String proxyHost = this.properties.get("proxy.host");
            String proxyType = this.properties.get("procy.type");
            if(proxyType!=null){
                Proxy proxy = new Proxy(Proxy.Type.valueOf(proxyType.toUpperCase()),
                        InetSocketAddress.createUnresolved(proxyHost, Integer.parseInt(proxyPort)));
                conn = itemToLoad.toURL().openConnection(proxy);
            }else{
                conn = itemToLoad.toURL().openConnection();
            }
            String timeout = this.properties.get("connection.connect.timeout");
            if(timeout!=null){
                int seconds = Integer.parseInt(timeout);
                conn.setConnectTimeout(seconds*1000);
            }else{
                conn.setConnectTimeout(10000);
            }
            timeout = this.properties.get("connection.read.timeout");
            if(timeout!=null){
                int seconds = Integer.parseInt(timeout);
                conn.setReadTimeout(seconds*1000);
            }else{
                conn.setReadTimeout(10000);
            }

            byte[] cachedData = new byte[4096];
            is = conn.getInputStream();
            int read = is.read(cachedData);
            while (read > 0) {
                stream.write(cachedData, 0, read);
                read = is.read(cachedData);
            }
            setData(stream.toByteArray());
            if (!fallbackLoad) {
                writeCache();
                lastLoaded = System.currentTimeMillis();
                loadCount.incrementAndGet();
            }
            return true;
        } catch (Exception e) {
            LOG.log(Level.INFO, "Failed to load resource input for " + resourceId + " from " + itemToLoad, e);
            return false;
        } finally {
            if (is!=null) {
                try {
                    is.close();
                } catch (Exception e) {
                    LOG.log(Level.INFO, "Error closing resource input for " + resourceId, e);
                }
            }
            try {
                stream.close();
            } catch (IOException e) {
                LOG.log(Level.INFO, "Error closing resource input for " + resourceId, e);
            }
        }
    }

    /**
     * Get the resource data. This will trigger a full load, if the resource is
     * not loaded, e.g. for LAZY resources.
     *
     * @return the data to load.
     */
    public final byte[] getData() {
        return getData(true);
    }

    protected byte[] getData(boolean loadIfNeeded) {
        byte[] result = this.data == null ? null : this.data.get();
        if (result == null && loadIfNeeded) {
            accessCount.incrementAndGet();
            byte[] currentData = this.data == null ? null : this.data.get();
            if (currentData==null) {
                synchronized (lock) {
                    currentData = this.data == null ? null : this.data.get();
                    if (currentData==null && shouldReadDataFromFallback()) {
                        loadFallback();
                    }
                }
            }
            currentData = this.data == null ? null : this.data.get();
            if (currentData==null) {
                throw new IllegalStateException("Failed to load remote as well as fallback resources for " + this);
            }
            return currentData.clone();
        }
        return result;
    }

    protected final void setData(byte[] bytes) {
        this.data = new SoftReference<>(bytes);
    }


    public void unload() {
        synchronized (lock) {
            int count = accessCount.decrementAndGet();
            if (count == 0) {
                this.data = null;
            }
        }
    }

    /**
     * Explicitly override the resource wih the fallback context and resets the
     * load counter.
     *
     * @return true on success.
     */
    public boolean resetToFallback() {
        if (loadFallback()) {
            loadCount.set(0);
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return "LoadableResource [resourceId=" + resourceId + ", fallbackLocation=" +
                fallbackLocation + ", remoteResources=" + remoteResources +
                ", loadCount=" + loadCount + ", accessCount=" + accessCount + ", lastLoaded=" + lastLoaded + ']';
    }

    /**
     * InputStream , that helps managing the load count.
     *
     * @author Anatole
     */
    private final class WrappedInputStream extends InputStream {

        private final InputStream wrapped;

        WrappedInputStream(InputStream wrapped) {
            this.wrapped = wrapped;
        }

        @Override
        public int read() throws IOException {
            return wrapped.read();
        }

        @Override
        public void close() throws IOException {
            try {
                wrapped.close();
                super.close();
            } finally {
                unload();
            }
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy