org.apache.ode.utils.HierarchicalProperties Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.ode.utils;
import org.apache.commons.collections.map.MultiKeyMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.StringUtils;
import javax.xml.namespace.QName;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
import java.util.Properties;
import java.util.regex.Pattern;
/**
* This class load a list of regular property files (order matters). The main feature is that property can
* be chained in three levels. Then when querying for a property, if it's not found in the deepest level,
* the parent will be queryed and so on.
*
* A prefix must be defined to discriminate the property name and the level-1, level-2 names. The default prefix is {@link #ODE_PREFFIX}.
*
* Properties must respect the following pattern: [level1.[level2.]prefix.]property
*
* A concrete use case could be the definition of properties for wsdl services and ports.
*
Level 0 would be: values common to all services and ports.
*
Level 1: values common to a given service.
*
Level 2: values common to a given port.
*
* For instance, if the property file looks like this:
*
*alias.foo_ns=http://foo.com
*
* timeout=40000
* a_namespace_with_no_alias_defined.film-service.port-of-cannes.ode.timeout=50000
*
* max-redirects=30
* foo_ns.brel-service.ode.max-redirects=40
* foo_ns.brel-service.port-of-amsterdam.ode.max-redirects=60
*
* The following values may be expected:
*
* getProperty("max-redirects") => 30
* getProperty("http://foo.com", "brel-service", "max-redirects") => 40
* getProperty("http://foo.com", "brel-service", "port-of-amsterdam", "max-redirects") => 60
*
* getProperty("a_namespace_with_no_alias_defined", "film-service", "timeout") => 40000
* getProperty("a_namespace_with_no_alias_defined", "film-service", "port-of-cannes", "timeout") => 50000
* getProperty("http://foo.com", "port-of-amsterdam", "timeout") => 40000
*
*
*
* Values may contain some environment variables. For instance, message=You're using ${java.version}.
*
*
* If a property name ends with ".file" or ".path", the assumption is made that the associated value is a path and as such is resolved against the path of the file it was loaded from.
*
* Assigned properties must not start with 'system.' or 'env.'. These prefix are reserved to access system properties and environment variables.
*
* @author Alexis Midon
*/
public class HierarchicalProperties {
private static final Log log = LogFactory.getLog(HierarchicalProperties.class);
public static final String ODE_PREFFIX = "ode";
private File[] files;
private String prefix;
private String dotted_prefix;
/*
This map contains ChainedMap instances chained according to the (qualified) service and/or port they are associated with.
All ChainedMap instances has a common parent.
The ChainedMap instances are chained to each others so that if a property is not found for [service, port],
the ChainedMap associated to [service] will be queried, and if still not found, then the common parent.
The ChainedMap instance common to all services and ports is associated to the [null, null] key.
ChainedMap instance common to all ports of a given service is associated to [service, null].
ChainedMap instance of a given service, port couple is associated to [service, port].
The ChainedMap instances contain string values as loaded from the filesystem.
*/
private MultiKeyMap hierarchicalMap = new MultiKeyMap();
// map used to cache immutable versions of the maps
private transient MultiKeyMap cacheOfImmutableMaps = new MultiKeyMap();
/**
* @param files the property file to be loaded. The file may not exist.
* But if the file exists it has to be a file (not a directory), otherwhise an IOException is thrown. Files will be loaded in the given order.
* @param prefix the property prefix
* @throws IOException
*/
public HierarchicalProperties(File[] files, String prefix) throws IOException {
this.files = files;
this.prefix = prefix;
this.dotted_prefix = "." + prefix + ".";
loadFiles();
}
public HierarchicalProperties(File[] files) throws IOException {
this(files, ODE_PREFFIX);
}
public HierarchicalProperties(File file, String prefix) throws IOException {
this(new File[]{file}, prefix);
}
public HierarchicalProperties(File file) throws IOException {
this(new File[]{file}, ODE_PREFFIX);
}
public HierarchicalProperties(List propFiles) throws IOException {
this(propFiles.toArray(new File[propFiles.size()]), ODE_PREFFIX);
}
/**
* Clear all existing content, then read the file and parse each property. Simply logs a message and returns if the file does not exist.
*
* @throws IOException if the file is a Directory
*/
public void loadFiles() throws IOException {
// #1. clear all existing content
clear();
// #3. put the root map
initRoot();
for (File file : files) {
Properties props = loadFile(file);
if(!props.isEmpty()) processProperties(props, file);
}
replacePlaceholders();
}
private ChainedMap initRoot() {
ChainedMap root = new ChainedMap();
hierarchicalMap.put(null, null, root);
return root;
}
private void processProperties(Properties props, File file) throws IOException {
validatePropertyNames(props, file);
Map nsByAlias = collectAliases(props, file);
// #4. process each property
for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
Map.Entry e = (Map.Entry) it.next();
String key = (String) e.getKey();
String value = (String) e.getValue();
// parse the property name
String[] info = parseProperty(key);
String nsalias = info[0];
String service = info[1];
String port = info[2];
String targetedProperty = info[3];
QName qname = null;
if (nsalias != null) {
qname = new QName(nsByAlias.get(nsalias) != null ? nsByAlias.get(nsalias) : nsalias, service);
}
// get the map associated to this port
ChainedMap p = (ChainedMap) hierarchicalMap.get(qname, port);
if (p == null) {
// create it if necessary
// get the associated service map
ChainedMap s = (ChainedMap) hierarchicalMap.get(qname, null);
if (s == null) {
// create the service map if necessary, the parent is the root map.
s = new ChainedMap(getRootMap());
// put it in the multi-map
hierarchicalMap.put(qname, null, s);
}
// create the map itself and link it to the service map
p = new ChainedMap(s);
// put it in the multi-map
hierarchicalMap.put(qname, port, p);
}
if(targetedProperty.endsWith(".file") || targetedProperty.endsWith(".path")){
String absolutePath = file.toURI().resolve(value).getPath();
if(log.isDebugEnabled()) log.debug("path: "+value+" resolved into: "+absolutePath);
value = absolutePath;
}
// save the key/value in its chained map
if(log.isDebugEnabled()) log.debug("New property: "+targetedProperty+" -> "+value);
p.put(targetedProperty, value);
}
}
private Properties loadFile(File file) throws IOException {
Properties props = new Properties();
if (!file.exists()) {
if (log.isDebugEnabled()) log.debug("File does not exist [" + file + "]");
return props;
}
// #2. read the file
FileInputStream fis = new FileInputStream(file);
try {
if (log.isDebugEnabled()) log.debug("Loading property file: " + file);
props.load(fis);
} finally {
fis.close();
}
return props;
}
private Map collectAliases(Properties props, File file) {
// gather all aliases
Map nsByAlias = new HashMap();
// replace env variable by their values and collect namespace aliases
for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
Map.Entry e = (Map.Entry) it.next();
String key = (String) e.getKey();
String value = (String) e.getValue();
if (key.startsWith("alias.")) {
// we found an namespace alias
final String alias = key.substring("alias.".length(), key.length());
if (log.isDebugEnabled()) log.debug("Alias found: " + alias + " -> " + value);
if (nsByAlias.containsKey(alias) && value.equals(nsByAlias.get(alias)))
throw new RuntimeException("Same alias used twice for 2 different namespaces! file=" + file + ", alias=" + alias);
nsByAlias.put(alias, value);
// remove the pair from the Properties
it.remove();
}
}
return nsByAlias;
}
private void validatePropertyNames(Properties props, File file) {
List invalids = new ArrayList();
for (Iterator
© 2015 - 2025 Weber Informatics LLC | Privacy Policy