org.eclipse.jetty.start.Props Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.start;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.start.Props.Prop;
import static org.eclipse.jetty.start.UsageException.ERR_BAD_ARG;
/**
* Management of Properties.
*
* This is larger in scope than the standard {@link java.util.Properties}, as it will also handle tracking the origin of each property, if it was overridden,
* and also allowing for ${property}
expansion.
*/
public final class Props implements Iterable
{
private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}");
public static class Prop
{
public String key;
public String value;
public String source;
public Prop(String key, String value, String source)
{
this.key = key;
this.value = value;
this.source = source;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("Prop [key=");
builder.append(key);
builder.append(", value=");
builder.append(value);
builder.append(", source=");
builder.append(source);
builder.append("]");
return builder.toString();
}
}
public static final String ORIGIN_SYSPROP = "";
public static String getValue(String arg)
{
int idx = arg.indexOf('=');
if (idx == (-1))
{
throw new UsageException(ERR_BAD_ARG, "Argument is missing a required value: %s", arg);
}
String value = arg.substring(idx + 1).trim();
if (value.length() <= 0)
{
throw new UsageException(ERR_BAD_ARG, "Argument is missing a required value: %s", arg);
}
return value;
}
public static List getValues(String arg)
{
String v = getValue(arg);
ArrayList l = new ArrayList<>();
for (String s : v.split(","))
{
if (s != null)
{
s = s.trim();
if (s.length() > 0)
{
l.add(s);
}
}
}
return l;
}
private Map props = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private List sysPropTracking = new ArrayList<>();
public void addAll(Props other)
{
this.props.putAll(other.props);
this.sysPropTracking.addAll(other.sysPropTracking);
}
/**
* Add a potential argument as a property.
*
* If arg is not a property, ignore it.
*
* @param arg the argument to parse for a potential property
* @param source the source for this argument (to track origin of property from)
* @return true if the property was added, false if the property wasn't added
*/
public boolean addPossibleProperty(String arg, String source)
{
// Start property (syntax similar to System property)
if (arg.startsWith("-D"))
{
String[] assign = arg.substring(2).split("=", 2);
switch (assign.length)
{
case 2:
setSystemProperty(assign[0], assign[1]);
setProperty(assign[0], assign[1], source);
return true;
case 1:
setSystemProperty(assign[0], "");
setProperty(assign[0], "", source);
return true;
default:
return false;
}
}
// Is this a raw property declaration?
int idx = arg.indexOf('=');
if (idx >= 0)
{
String key = arg.substring(0, idx);
String value = arg.substring(idx + 1);
setProperty(key, value, source);
return true;
}
// All other strings are ignored
return false;
}
public String cleanReference(String property)
{
String name = property.trim();
if (name.startsWith("${") && name.endsWith("}"))
{
name = name.substring(2, name.length() - 1);
}
return name.trim();
}
public boolean containsKey(String key)
{
Prop prop = props.get(key);
return prop != null && prop.value != null;
}
public String expand(String str)
{
return expand(str, new Stack());
}
private String expand(String str, Stack seenStack)
{
if (str == null)
{
return str;
}
if (str.indexOf("${") < 0)
{
// Contains no potential expressions.
return str;
}
Matcher mat = __propertyPattern.matcher(str);
StringBuilder expanded = new StringBuilder();
int offset = 0;
String property;
String value;
while (mat.find(offset))
{
property = mat.group(1);
// Loop detection
if (seenStack.contains(property))
{
StringBuilder err = new StringBuilder();
err.append("Property expansion loop detected: ");
int idx = seenStack.lastIndexOf(property);
for (int i = idx; i < seenStack.size(); i++)
{
err.append(seenStack.get(i));
err.append(" -> ");
}
err.append(property);
throw new PropsException(err.toString());
}
// find property name
expanded.append(str.subSequence(offset, mat.start()));
// get property value
value = getString(property);
if (value == null)
{
StartLog.trace("Unable to expand: %s", property);
expanded.append(mat.group());
}
else
{
// recursively expand
seenStack.push(property);
value = expand(value, seenStack);
seenStack.pop();
expanded.append(value);
}
// update offset
offset = mat.end();
}
// leftover
expanded.append(str.substring(offset));
// special case for "$$"
if (expanded.indexOf("$$") >= 0)
{
return expanded.toString().replaceAll("\\$\\$", "\\$");
}
return expanded.toString();
}
public Prop getProp(String key)
{
return getProp(key, true);
}
public Prop getProp(String key, boolean searchSystemProps)
{
Prop prop = props.get(key);
if ((prop == null) && searchSystemProps)
{
// try system property
prop = getSystemProperty(key);
}
return prop;
}
public String getString(String key)
{
if (key == null)
{
throw new PropsException("Cannot get value for null key");
}
String name = cleanReference(key);
if (name.length() == 0)
{
throw new PropsException("Cannot get value for empty key");
}
Prop prop = getProp(name);
if (prop == null)
{
return null;
}
return prop.value;
}
public String getString(String key, String defVal)
{
String val = getString(key);
if (val == null)
{
return defVal;
}
return val;
}
private Prop getSystemProperty(String key)
{
String value = System.getProperty(key);
if (value == null)
{
return null;
}
return new Prop(key, value, ORIGIN_SYSPROP);
}
public static boolean hasPropertyKey(String name)
{
return __propertyPattern.matcher(name).find();
}
@Override
public Iterator iterator()
{
return props.values().iterator();
}
public void reset()
{
props.clear();
}
public void setProperty(Prop prop)
{
props.put(prop.key, prop);
}
public void setProperty(String key, String value, String origin)
{
props.put(key, new Prop(key, value, origin));
}
public int size()
{
return props.size();
}
public void store(OutputStream stream, String comments) throws IOException
{
Properties props = new Properties();
// add all Props as normal properties, with expansion performed.
for (Prop prop : this)
{
props.setProperty(prop.key, expand(prop.value));
}
// write normal properties file
props.store(stream, comments);
}
public void setSystemProperty(String key, String value)
{
System.setProperty(key, value);
sysPropTracking.add(key);
}
@Override
public String toString()
{
return props.toString();
}
public static Props load(ClassLoader classLoader, String resourceName)
{
StartLog.debug("Looking for classloader resource: %s", resourceName);
return load(classLoader.getResource(resourceName));
}
public static Props load(URL url)
{
Props props = new Props();
if (url != null)
{
StartLog.debug("Loading Props: %s", url.toExternalForm());
try (InputStream in = url.openStream())
{
Properties properties = new Properties();
properties.load(in);
String urlStr = url.toExternalForm();
properties.stringPropertyNames().forEach((name) ->
props.setProperty(name, properties.getProperty(name), urlStr));
}
catch (IOException x)
{
StartLog.debug(x);
}
}
return props;
}
}