aboutsummaryrefslogtreecommitdiff
path: root/pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java
diff options
context:
space:
mode:
Diffstat (limited to 'pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java')
-rw-r--r--pdf-as-lib/src/main/java/at/knowcenter/wag/egov/egiz/cfg/NestedProperties.java255
1 files changed, 255 insertions, 0 deletions
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> Copyright 2006 by Know-Center, Graz, Austria </copyright>
+ * 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.<br/>
+ * In order to include further Properties use the following instruction:
+ * <p/>
+ * <code>include = [path/to/]foo.properties</code>
+ * <p/>
+ * Note that wildcard imports are allowed, e.g.
+ * <p/>
+ * <code>include = [path/to/]profile.*.properties</code>
+ * <p/>
+ * In order to use more than one include instruction within a file append an arbitary postfix to <code>include.</code>
+ * in order to make each include key unique within a properties file, e.g.
+ * <p/>
+ * <code>include = profile.SIGNATURBLOCK*.properties</code><br/>
+ * <code>include.amtssignaturen = profile.AMTSSIGNATURBLOCK*.properties</code><br/>
+ * <code>include.1 = myProfiles1/*.properties</code><br/>
+ * <code>include.2 = myProfiles2/*.properties</code>
+ * <p/>
+ * Note that
+ * <ul>
+ * <li>all include instructions of a certain properties file are sorted alphabetically by their key name before being
+ * executed,</li>
+ * <li>include instructions can be used within any properties files being loaded (even within other includes) and that</li>
+ * <li>(wildcard) path/file declarations are always case <b>insensitive</b>, regardless of the underlying operating
+ * system.</li>
+ * <li>included properties always override locally defined properties</li>
+ * </ul>
+ * <p/>
+ * <b>Mind creating circular includes!</b>
+ *
+ * @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<String, String> sortedIncludeInstructions = new TreeMap<String, String>();
+
+ // 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<String> propertyNames = (Enumeration<String>) 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<String> 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<File> 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() + "'.");
+ }
+ }
+
+}