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

org.mapfish.print.servlet.ServletMapPrinterFactory Maven / Gradle / Ivy

There is a newer version: 3.22.0
Show newest version
package org.mapfish.print.servlet;

import org.apache.commons.io.DirectoryWalker;
import org.apache.commons.lang3.StringUtils;
import org.locationtech.jts.util.Assert;
import org.mapfish.print.MapPrinter;
import org.mapfish.print.MapPrinterFactory;
import org.mapfish.print.servlet.fileloader.ConfigFileLoaderManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.ClosedByInterruptException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;

/**
 * A {@link org.mapfish.print.MapPrinterFactory} that reads configuration from files and uses servlet's
 * methods for resolving the paths to the files.
 * 

*/ public class ServletMapPrinterFactory implements MapPrinterFactory { /** * The name of the default app. This is always required to be one of the apps that are registered. */ public static final String DEFAULT_CONFIGURATION_FILE_KEY = "default"; private static final String CONFIG_YAML = "config.yaml"; private static final Logger LOGGER = LoggerFactory.getLogger(ServletMapPrinterFactory.class); private static final int MAX_DEPTH = 2; private final Map printers = new HashMap<>(); private final Map configurationFileLastModifiedTimes = new HashMap<>(); private final Map configurationFiles = new HashMap<>(); @Autowired private ApplicationContext applicationContext; @Autowired private ConfigFileLoaderManager configFileLoader; private String appsRootDirectory = null; @PostConstruct private void validateConfigurationFiles() { for (URI file: this.configurationFiles.values()) { Assert.isTrue(this.configFileLoader.isAccessible(file), file + " does not exist or is not accessible."); } } @Override public final synchronized MapPrinter create(@Nullable final String app) throws NoSuchAppException { String finalApp = app; if (app == null) { finalApp = DEFAULT_CONFIGURATION_FILE_KEY; } URI configFile = this.configurationFiles.get(finalApp); if (configFile == null) { configFile = checkForAddedApp(finalApp); } if (configFile == null) { throw new NoSuchAppException( "There is no configurationFile registered in the " + getClass().getName() + " bean with the id: '" + finalApp + "'"); } final long lastModified = this.configurationFileLastModifiedTimes.getOrDefault(finalApp, 0L); MapPrinter printer = this.printers.get(finalApp); final Optional configFileLastModified; try { configFileLastModified = this.configFileLoader.lastModified(configFile); } catch (NoSuchElementException e) { // the app has been removed this.configurationFiles.remove(finalApp); this.configurationFileLastModifiedTimes.remove(finalApp); this.printers.remove(finalApp); if (finalApp.equals(DEFAULT_CONFIGURATION_FILE_KEY)) { pickDefaultApp(); } throw new NoSuchAppException( "There is no configurationFile registered in the " + getClass().getName() + " bean with the id: '" + finalApp + "'"); } if (configFileLastModified.isPresent() && configFileLastModified.get() > lastModified) { // file modified, reload it LOGGER.info("Configuration file modified. Reloading..."); this.printers.remove(finalApp); printer = null; } if (printer == null) { if (configFileLastModified.isPresent()) { this.configurationFileLastModifiedTimes.put(finalApp, configFileLastModified.get()); } try { LOGGER.info("Loading configuration file: {}", configFile); printer = this.applicationContext.getBean(MapPrinter.class); byte[] bytes = this.configFileLoader.loadFile(configFile); printer.setConfiguration(configFile, bytes); this.printers.put(finalApp, printer); } catch (ClosedByInterruptException e) { // because of a bug in the JDK, the interrupted status might not be set // when throwing a ClosedByInterruptException. so, we do it manually. // see also http://bugs.java.com/view_bug.do?bug_id=7043425 Thread.currentThread().interrupt(); LOGGER.error("Error occurred while reading configuration file '{}'", configFile); throw new RuntimeException(String.format( "Error occurred while reading configuration file '%s': ", configFile), e); } catch (Throwable e) { LOGGER.error("Error occurred while reading configuration file '{}'", configFile); throw new RuntimeException(String.format( "Error occurred while reading configuration file '%s'", configFile), e); } } return printer; } @Override public final Set getAppIds() { return this.configurationFiles.keySet(); } /** * The setter for setting configuration file. It will convert the value to a URI. * * @param configurationFiles the configuration file map. */ public final void setConfigurationFiles(final Map configurationFiles) throws URISyntaxException { this.configurationFiles.clear(); this.configurationFileLastModifiedTimes.clear(); for (Map.Entry entry: configurationFiles.entrySet()) { if (!entry.getValue().contains(":/")) { // assume is a file this.configurationFiles.put(entry.getKey(), new File(entry.getValue()).toURI()); } else { this.configurationFiles.put(entry.getKey(), new URI(entry.getValue())); } } if (this.configFileLoader != null) { this.validateConfigurationFiles(); } } /** * Set a single directory that contains one or more subdirectories, each one that contains a config.yaml * file will be considered a print app. *

* This can be called multiple times and each directory will add to the apps found in the other * directories. However the appId is based on the directory names so if there are 2 directories with the * same name the second will overwrite the first encounter. * * @param directory the root directory containing the sub-app-directories. This must resolve to a * file with the */ public final void setAppsRootDirectory(final String directory) throws URISyntaxException, IOException { this.appsRootDirectory = directory; final File realRoot; if (!directory.contains(":/")) { realRoot = new File(directory); } else { final Optional fileOptional = this.configFileLoader.toFile(new URI(directory)); if (fileOptional.isPresent()) { realRoot = fileOptional.get(); } else { throw new IllegalArgumentException( directory + " does not refer to a file on the current system."); } } final AppWalker walker = new AppWalker(); for (File child: walker.getAppDirs(realRoot)) { final File configFile = new File(child, CONFIG_YAML); String appName = realRoot.toURI().relativize(child.toURI()).getPath().replace('/', ':'); if (appName.endsWith(":")) { appName = appName.substring(0, appName.length() - 1); } this.configurationFiles.put(appName, configFile.toURI()); } if (this.configurationFiles.isEmpty()) { return; } // ensure there is a "default" app if (!this.configurationFiles.containsKey(DEFAULT_CONFIGURATION_FILE_KEY)) { pickDefaultApp(); } } private void pickDefaultApp() { final Iterator> iterator = this.configurationFiles.entrySet().iterator(); if (iterator.hasNext()) { final Map.Entry next = iterator.next(); final URI uri = next.getValue(); this.configurationFiles.put(DEFAULT_CONFIGURATION_FILE_KEY, uri); } } @Nullable private URI checkForAddedApp(@Nonnull final String app) { if (this.appsRootDirectory == null) { return null; } if (StringUtils.countMatches(app, ":") > MAX_DEPTH) { return null; } final Optional child; try { child = this.configFileLoader.toFile(new URI(this.appsRootDirectory + "/" + app.replace(':', '/'))); } catch (URISyntaxException e) { return null; } if (child.isPresent()) { final File configFile = new File(child.get(), CONFIG_YAML); if (configFile.exists()) { final URI uri = configFile.toURI(); this.configurationFiles.put(app, uri); if (!this.configurationFiles.containsKey(DEFAULT_CONFIGURATION_FILE_KEY)) { this.configurationFiles.put(DEFAULT_CONFIGURATION_FILE_KEY, uri); } return uri; } } return null; } private static class AppWalker extends DirectoryWalker { public List getAppDirs(final File base) throws IOException { List results = new ArrayList<>(); walk(base, results); return results; } @Override protected boolean handleDirectory( final File directory, final int depth, final Collection results) { final File configFile = new File(directory, CONFIG_YAML); if (configFile.exists()) { results.add(directory); } return depth < MAX_DEPTH; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy