Package org.opentripplanner.routing.impl

Source Code of org.opentripplanner.routing.impl.InputStreamGraphSource$FileFactory

/* This program 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 (props, at your option) any later version.

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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.opentripplanner.routing.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.prefs.Preferences;

import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Graph.LoadLevel;
import org.opentripplanner.routing.services.GraphSource;
import org.opentripplanner.routing.services.StreetVertexIndexFactory;
import org.opentripplanner.updater.GraphUpdaterConfigurator;
import org.opentripplanner.updater.PropertiesPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.ByteStreams;

/**
* The primary implementation of the GraphSource interface. The graph is loaded from a serialized
* graph from a given source.
*
*/
public class InputStreamGraphSource implements GraphSource {

    public static final String GRAPH_FILENAME = "Graph.obj";

    public static final String CONFIG_FILENAME = "Graph.properties";

    private static final Logger LOG = LoggerFactory.getLogger(InputStreamGraphSource.class);

    /**
     * Delay before starting to load a graph after the last modification time. In case of writing,
     * we expect graph last modification time to be updated at at least that frequency. If not, you
     * can either increase this value, or use an atomic move when copying the file.
     * */
    private static final long LOAD_DELAY_SEC = 10;

    private Graph graph;

    private String routerId;

    private long graphLastModified = 0L;

    private LoadLevel loadLevel;

    private Object preEvictMutex = new Boolean(false);

    /**
     * The current used input stream implementation for getting graph data source.
     */
    private GraphInputStream graphInputStream;

    // TODO Why do we need a factory? There is a single one implementation.
    private StreetVertexIndexFactory streetVertexIndexFactory = new DefaultStreetVertexIndexFactory();

    private GraphUpdaterConfigurator configurator = new GraphUpdaterConfigurator();

    /**
     * @param routerId
     * @param path
     * @param loadLevel
     * @return A GraphSource loading graph from the file system under a base path.
     */
    public static InputStreamGraphSource newFileGraphSource(String routerId, File path,
            LoadLevel loadLevel) {
        return new InputStreamGraphSource(routerId, loadLevel, new FileGraphInputStream(path));
    }

    /**
     * @param routerId
     * @param path
     * @param loadLevel
     * @return A GraphSource loading graph from an embedded classpath resources (a graph bundled
     *         inside a pre-packaged WAR for example).
     */
    public static InputStreamGraphSource newClasspathGraphSource(String routerId, File path,
            LoadLevel loadLevel) {
        return new InputStreamGraphSource(routerId, loadLevel, new ClasspathGraphInputStream(path));
    }

    private InputStreamGraphSource(String routerId, LoadLevel loadLevel,
            GraphInputStream graphInputStream) {
        this.routerId = routerId;
        this.loadLevel = loadLevel;
        this.graphInputStream = graphInputStream;
        this.reload(true, false);
    }

    @Override
    public Graph getGraph() {
        /*
         * We synchronize on pre-evict mutex in case we are in the middle of reloading in pre-evict
         * mode. In that case we must make the client wait until the new graph is loaded, because
         * the old one is gone to the GC. Performance hit should be low as getGraph() is not called
         * often.
         */
        synchronized (preEvictMutex) {
            return graph;
        }
    }

    @Override
    public boolean reload(boolean force, boolean preEvict) {
        /* We synchronize on 'this' to prevent multiple reloads from being called at the same time */
        synchronized (this) {
            long lastModified = graphInputStream.getLastModified();
            boolean doReload = force ? true : checkAutoReload(lastModified);
            if (!doReload)
                return true;
            if (preEvict) {
                synchronized (preEvictMutex) {
                    if (graph != null)
                        configurator.shutdownGraph(graph);
                    /*
                     * Forcing graph to null here should remove any references to the graph once all
                     * current requests are done. So the next reload is supposed to have more
                     * memory.
                     */
                    graph = null;
                    graph = loadGraph();
                }
            } else {
                Graph newGraph = loadGraph();
                if (newGraph != null) {
                    // Load OK
                    if (graph != null)
                        configurator.shutdownGraph(graph);
                    graph = newGraph; // Assignment in java is atomic
                } else {
                    // Load failed
                    if (force || graph == null) {
                        LOG.warn("Unable to load data for router '{}'.", routerId);
                        if (graph != null)
                            configurator.shutdownGraph(graph);
                        graph = null;
                    } else {
                        // No shutdown, since we keep current one.
                        LOG.warn("Unable to load data for router '{}', keeping old data.", routerId);
                    }
                }
            }
            if (graph == null) {
                graphLastModified = 0L;
            } else {
                /*
                 * Note: we flag even if loading failed, because we want to wait for fresh new data
                 * before loading again.
                 */
                graphLastModified = lastModified;
            }
            // If a graph is null, it will be evicted.
            return (graph != null);
        }
    }

    /**
     * Check if a graph has been modified since the last time it has been loaded.
     *
     * @param lastModified Time of last modification of current loaded data.
     * @return True if the input data has been modified and need to be reloaded.
     */
    private boolean checkAutoReload(long lastModified) {
        // We check only for graph file modification, not config
        long validEndTime = System.currentTimeMillis() - LOAD_DELAY_SEC * 1000;
        LOG.debug(
                "checkAutoReload router '{}' validEndTime={} lastModified={} graphLastModified={}",
                routerId, validEndTime, lastModified, graphLastModified);
        if (lastModified != graphLastModified && lastModified <= validEndTime) {
            // Only reload graph modified more than 1 mn ago.
            LOG.info("Router ID '{}' graph input modification detected, force reload.", routerId);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void evict() {
        synchronized (this) {
            if (graph != null) {
                configurator.shutdownGraph(graph);
            }
        }
    }

    /**
     * Do the actual operation of graph loading. Load configuration if present, and configure the
     * graph with dynamic updaters.
     *
     * @return
     */
    private Graph loadGraph() {
        final Graph graph;
        try (InputStream is = graphInputStream.getGraphInputStream()) {
            LOG.info("Loading graph...");
            try {
                graph = Graph.load(new ObjectInputStream(is), loadLevel, streetVertexIndexFactory);
            } catch (Exception ex) {
                LOG.error("Exception while loading graph '{}'.", routerId);
                ex.printStackTrace();
                return null;
            }

            graph.routerId = (routerId);
        } catch (IOException e) {
            LOG.warn("Graph file not found or not openable for routerId '{}': {}", routerId, e);
            return null;
        }

        // Decorate the graph. Even if a config file is not present
        // one could be bundled inside.
        try (InputStream is = graphInputStream.getConfigInputStream()) {
            Preferences config = is == null ? null : new PropertiesPreferences(is);
            configurator.setupGraph(graph, config);
        } catch (IOException e) {
            LOG.error("Can't read config file", e);
        }
        return graph;
    }

    /**
     * InputStreamGraphSource delegates to some actual implementation the fact of getting the input
     * stream and checking the last modification timestamp for a given routerId.
     */
    private interface GraphInputStream {
        public abstract InputStream getGraphInputStream() throws IOException;

        public abstract InputStream getConfigInputStream() throws IOException;

        public abstract long getLastModified();
    }

    private static class FileGraphInputStream implements GraphInputStream {

        private File path;

        private FileGraphInputStream(File path) {
            this.path = path;
        }

        @Override
        public InputStream getGraphInputStream() throws IOException {
            File graphFile = new File(path, GRAPH_FILENAME);
            LOG.debug("Loading graph from file '{}'", graphFile.getPath());
            return new FileInputStream(graphFile);
        }

        @Override
        public InputStream getConfigInputStream() throws IOException {
            File configFile = new File(path, CONFIG_FILENAME);
            if (configFile.canRead()) {
                LOG.debug("Loading config from file '{}'", configFile.getPath());
                return new FileInputStream(configFile);
            } else {
                return null;
            }
        }

        @Override
        public long getLastModified() {
            // Note: this returns 0L if the file does not exists
            return new File(path, GRAPH_FILENAME).lastModified();
        }
    }

    private static class ClasspathGraphInputStream implements GraphInputStream {

        private File path;

        private ClasspathGraphInputStream(File path) {
            this.path = path;
        }

        @Override
        public InputStream getGraphInputStream() {
            File graphFile = new File(path, GRAPH_FILENAME);
            LOG.debug("Loading graph from classpath at '{}'", graphFile.getPath());
            return Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream(graphFile.getPath());
        }

        @Override
        public InputStream getConfigInputStream() {
            File configFile = new File(path, CONFIG_FILENAME);
            LOG.debug("Trying to load config on classpath at '{}'", configFile.getPath());
            return Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream(configFile.getPath());
        }

        /**
         * For a packaged classpath resources we assume the data won't change, so returning always
         * 0L basically disable auto-reload in that case.
         */
        @Override
        public long getLastModified() {
            return 0L;
        }
    }

    /**
     * A GraphSource factory creating InputStreamGraphSource from file.
     *
     * @see FileGraphSource
     */
    public static class FileFactory implements GraphSource.Factory {

        private static final Logger LOG = LoggerFactory.getLogger(FileFactory.class);

        public File basePath = new File("/var/otp/graphs");

        public LoadLevel loadLevel = LoadLevel.FULL;

        @Override
        public GraphSource createGraphSource(String routerId) {
            return InputStreamGraphSource.newFileGraphSource(routerId, getBasePath(routerId),
                    loadLevel);
        }

        @Override
        public boolean save(String routerId, InputStream is) {

            File sourceFile = new File(getBasePath(routerId), InputStreamGraphSource.GRAPH_FILENAME);

            try {

                // Create directory if necessary
                File directory = new File(sourceFile.getParentFile().getPath());
                if (!directory.exists()) {
                    directory.mkdir();
                }

                // Store the stream to disk, to be sure no data will be lost make a temporary backup
                // file of the original file.

                // Make backup file
                File destFile = null;
                if (sourceFile.exists()) {
                    destFile = new File(sourceFile.getPath() + ".bak");
                    if (destFile.exists()) {
                        destFile.delete();
                    }
                    sourceFile.renameTo(destFile);
                }

                // Store the stream
                try (FileOutputStream os = new FileOutputStream(sourceFile)) {
                    ByteStreams.copy(is, os);
                }

                // And delete the backup file
                sourceFile = new File(sourceFile.getPath() + ".bak");
                if (sourceFile.exists()) {
                    sourceFile.delete();
                }

            } catch (Exception ex) {
                LOG.error("Exception while storing graph to {}.", sourceFile.getPath());
                ex.printStackTrace();
                return false;
            }

            return true;
        }

        private File getBasePath(String routerId) {
            return new File(basePath, routerId);
        }
    }
}
TOP

Related Classes of org.opentripplanner.routing.impl.InputStreamGraphSource$FileFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.