From db52e4d66d60184d53a27ba4d6772461daacc03d Mon Sep 17 00:00:00 2001 From: tknall Date: Fri, 22 Mar 2013 08:57:51 +0000 Subject: Maintenance update (bugfixes, new features, cleanup...) Refer to /dok/RELEASE_NOTES-3.3.txt for further information. git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/pdf-as/trunk@931 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- .../wag/egov/egiz/cfg/NestedProperties.java | 255 +++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java (limited to 'pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java') diff --git a/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java new file mode 100644 index 0000000..259a1dc --- /dev/null +++ b/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java @@ -0,0 +1,255 @@ +/** + * Copyright 2006 by Know-Center, Graz, Austria + * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a + * joint initiative of the Federal Chancellery Austria and Graz University of + * Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ +package at.knowcenter.wag.egov.egiz.cfg; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Enumeration; +import java.util.InvalidPropertiesFormatException; +import java.util.Iterator; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOCase; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.WildcardFileFilter; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Enhanced Java Properties allowing nested include instructions.
+ * In order to include further Properties use the following instruction: + *

+ * include = [path/to/]foo.properties + *

+ * Note that wildcard imports are allowed, e.g. + *

+ * include = [path/to/]profile.*.properties + *

+ * In order to use more than one include instruction within a file append an arbitary postfix to include. + * in order to make each include key unique within a properties file, e.g. + *

+ * include = profile.SIGNATURBLOCK*.properties
+ * include.amtssignaturen = profile.AMTSSIGNATURBLOCK*.properties
+ * include.1 = myProfiles1/*.properties
+ * include.2 = myProfiles2/*.properties + *

+ * Note that + *

+ *

+ * Mind creating circular includes! + * + * @author Datentechnik Innovation GmbH + */ +public class NestedProperties extends Properties { + + private static final long serialVersionUID = 1L; + + private Log log = LogFactory.getLog(getClass()); + + /** + * Creates an empty property list with no default values. + */ + public NestedProperties() { + super(); + } + + /** + * Creates an empty property list with the specified defaults. + * @param defaults The defaults. + */ + public NestedProperties(Properties defaults) { + super(defaults); + } + + /** + * The name of the key that triggers including of other properties. + */ + private final String INCLUDE_KEY_NAME = "include"; + + /** + * Defines the default behaviour of the file matching filter. + */ + private final IOCase DEFAULT_IOCASE = IOCase.SENSITIVE; + + /** + * The maximum depth of includes before being regarded as circular (throwing a {@link CircularIncludeException}). + */ + private final int MAX_NESTED_INCLUDE_DEPTH = 25; + + @Override + /** + * Warning: When Properties are loaded using InputStreams include instructions are not supported. + */ + public synchronized void load(InputStream inStream) throws IOException { + log.debug("Loading properties from input stream. Include instructions are not supported."); + super.load(inStream); + } + + @Override + public synchronized void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException { + // Loading from InputStream is not supported since including further property files that have been declared + // using relative paths need a context directory which cannot be retrieved from InputStreams. + throw new UnsupportedOperationException("Imports from XML files are not supported."); + } + + /** + * Reads a property list from a certain file including other properties files if include instructions are present. + * Note that include instructions that do not match any files do not result in an exception. A respective message at + * WARN level is logged. + * + * @param file + * The file to be read. + * @throws IOException + * Thrown in case of an I/O error. + * @throws CircularIncludeException + * Thrown if circular includes have been detected (@link {@link RuntimeException}). + */ + public synchronized void load(File file) throws IOException, CircularIncludeException { + load(file, 0); + } + + /** + * Reads a property list from a certain file including other properties files if include instructions are present. + * Note that include instructions that do not match any files do not result in an exception. A respective message at + * WARN level is logged. + * + * @param file + * The file to be read. + * @param currentDepth + * The current include depth. + * @throws IOException + * Thrown in case of an I/O error. + * @throws CircularIncludeException + * Thrown if circular includes have been detected (@link {@link RuntimeException}). + */ + private synchronized void load(File file, int currentDepth) throws IOException, CircularIncludeException { + if (currentDepth > MAX_NESTED_INCLUDE_DEPTH) { + throw new CircularIncludeException("Circular include instruction(s) detected."); + } + InputStream in = null; + try { + in = new FileInputStream(file); + log.debug("Loading '" + file.getCanonicalPath() + "'."); + super.load(in); + } finally { + IOUtils.closeQuietly(in); + } + // Properties have been loaded. Apply preprocessing step in order to process include instructions. + // Provide a context directory in order to be able to resolve relative path instructions. + processIncludes(file.getParentFile(), currentDepth); + } + + /** + * Resolves all include instructions as part of a postprocessing step. + * + * @param contextFolder + * The folder that should be assumed as starting folder for relative include instructions. + * @param currentDepth + * The current include depth. + * @throws IOException + * Thrown in case of error. + */ + private void processIncludes(File contextFolder, int currentDepth) throws IOException { + SortedMap sortedIncludeInstructions = new TreeMap(); + + // Walk through properties, collecting include instructions. + // Since the backing Hashtable does not guarantee any order, import instructions need to be sorted according to + // their keys (natural order -> alphabetically). + // This allows for defining a pseudo load order: include.1=path/to/settings.propertes, + // include.2=other/path/to/settings.properties + @SuppressWarnings("unchecked") + Enumeration propertyNames = (Enumeration) propertyNames(); + while (propertyNames.hasMoreElements()) { + String key = propertyNames.nextElement(); + // valid include instructions: include=xxx, include.foo=xxx, include.foo.foo=xxx... (keys are case + // insensitive) + if (INCLUDE_KEY_NAME.equalsIgnoreCase(key) || StringUtils.startsWithIgnoreCase(key, INCLUDE_KEY_NAME + ".")) { + String includeValue = StringUtils.trimToNull(getProperty(key)); + if (includeValue != null) { + sortedIncludeInstructions.put(key, includeValue); + } + } + } + + // performing imports + Iterator includeIt = sortedIncludeInstructions.keySet().iterator(); + while (includeIt.hasNext()) { + String includeInstructionKey = includeIt.next(); + String includePath = getProperty(includeInstructionKey); + processInclude(contextFolder, includePath, currentDepth); + // remove import instruction from properties + remove(includeInstructionKey); + } + } + + /** + * Processes a single include instruction (which may lead to several imports due to wildcard support). + * + * @param contextFolder + * The folder that should be assumed as starting folder for relative include instructions. + * @param includePath + * The include path instruction. + * @param currentDepth + * The current include depth. + * @throws IOException + * Thrown in case of error. + */ + private void processInclude(File contextFolder, String includePath, int currentDepth) throws IOException { + // Combine contextFolder with relative path instructions from includePath. + File includeInstruction = new File(contextFolder, includePath); + contextFolder = includeInstruction.getParentFile(); + String includeName = includeInstruction.getName(); + + WildcardFileFilter fileFilter = new WildcardFileFilter(includeName, DEFAULT_IOCASE); + Collection includeFiles = null; + if (contextFolder != null && contextFolder.exists() && contextFolder.isDirectory()) { + includeFiles = FileUtils.listFiles(contextFolder, fileFilter, null); + } + if (includeFiles != null && !includeFiles.isEmpty()) { + log.info("Including '" + includePath + "'."); + for (File includeFile : includeFiles) { + NestedProperties includeProperties = new NestedProperties(); + includeProperties.load(includeFile, currentDepth + 1); + putAll(includeProperties); + } + } else { + log.warn("Unable to find '" + includeName + "' in folder '" + contextFolder.getCanonicalPath() + "'."); + } + } + +} -- cgit v1.2.3