| /* |
| * 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.openjpa.lib.conf; |
| |
| import java.io.File; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.Properties; |
| import java.util.TreeSet; |
| |
| import javax.naming.Context; |
| import javax.naming.InitialContext; |
| import javax.naming.NamingException; |
| |
| import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.ClassUtil; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.Options; |
| import org.apache.openjpa.lib.util.ParseException; |
| import org.apache.openjpa.lib.util.StringDistance; |
| import org.apache.openjpa.lib.util.StringUtil; |
| import org.apache.openjpa.lib.util.concurrent.ConcurrentReferenceHashMap; |
| |
| |
| /** |
| * Utility methods dealing with configuration. |
| * |
| * @author Abe White |
| */ |
| public class Configurations { |
| |
| private static final Localizer _loc = Localizer.forPackage(Configurations.class); |
| |
| private static final ConcurrentReferenceHashMap _loaders = new |
| ConcurrentReferenceHashMap(ReferenceStrength.WEAK, ReferenceStrength.HARD); |
| |
| private static final Object NULL_LOADER = "null-loader"; |
| |
| public static final String CONFIG_RESOURCE_PATH = "configResourcePath"; |
| public static final String CONFIG_RESOURCE_ANCHOR = "configResourceAnchor"; |
| |
| /** |
| * Return the class name from the given plugin string, or null if none. |
| */ |
| public static String getClassName(String plugin) { |
| return getPluginComponent(plugin, true); |
| } |
| |
| /** |
| * Return the properties part of the given plugin string, or null if none. |
| */ |
| public static String getProperties(String plugin) { |
| return getPluginComponent(plugin, false); |
| } |
| |
| /** |
| * Return either the class name or properties string from a plugin string. |
| */ |
| private static String getPluginComponent(String plugin, boolean clsName) { |
| if (plugin != null) |
| plugin = plugin.trim(); |
| if (StringUtil.isEmpty(plugin)) |
| return null; |
| |
| int openParen = -1; |
| if (plugin.charAt(plugin.length() - 1) == ')') |
| openParen = plugin.indexOf('('); |
| if (openParen == -1) { |
| int eq = plugin.indexOf('='); |
| if (eq == -1) |
| return (clsName) ? plugin : null; |
| return (clsName) ? null : plugin; |
| } |
| |
| // clsName(props) form |
| if (clsName) |
| return plugin.substring(0, openParen).trim(); |
| String prop = plugin.substring(openParen + 1, |
| plugin.length() - 1).trim(); |
| return (prop.length() == 0) ? null : prop; |
| } |
| |
| /** |
| * Combine the given class name and properties into a plugin string. |
| */ |
| public static String getPlugin(String clsName, String props) { |
| if (StringUtil.isEmpty(clsName)) |
| return props; |
| if (StringUtil.isEmpty(props)) |
| return clsName; |
| return clsName + "(" + props + ")"; |
| } |
| |
| /** |
| * Return a plugin string that combines the properties of the given plugin |
| * strings, where properties of <code>override</code> will override the |
| * same properties of <code>orig</code>. |
| */ |
| public static String combinePlugins(String orig, String override) { |
| if (StringUtil.isEmpty(orig)) |
| return override; |
| if (StringUtil.isEmpty(override)) |
| return orig; |
| |
| String origCls = getClassName(orig); |
| String overrideCls = getClassName(override); |
| String cls; |
| if (StringUtil.isEmpty(origCls)) |
| cls = overrideCls; |
| else if (StringUtil.isEmpty(overrideCls)) |
| cls = origCls; |
| else if (!origCls.equals(overrideCls)) |
| return override; // completely different plugin |
| else |
| cls = origCls; |
| |
| String origProps = getProperties(orig); |
| String overrideProps = getProperties(override); |
| if (StringUtil.isEmpty(origProps)) |
| return getPlugin(cls, overrideProps); |
| if (StringUtil.isEmpty(overrideProps)) |
| return getPlugin(cls, origProps); |
| |
| Properties props = parseProperties(origProps); |
| props.putAll(parseProperties(overrideProps)); |
| return getPlugin(cls, serializeProperties(props)); |
| } |
| |
| /** |
| * Create the instance with the given class name, using the given |
| * class loader. No configuration of the instance is performed by |
| * this method. |
| */ |
| public static Object newInstance(String clsName, ClassLoader loader) { |
| return newInstance(clsName, null, null, loader, true); |
| } |
| |
| /** |
| * Create and configure an instance with the given class name and |
| * properties as a String. |
| */ |
| public static Object newInstance(String clsName, Configuration conf, |
| String props, ClassLoader loader) { |
| Object obj = newInstance(clsName, null, conf, loader, true); |
| configureInstance(obj, conf, props); |
| return obj; |
| } |
| |
| /** |
| * Create and configure an instance with the given class name and |
| * properties. |
| */ |
| public static Object newInstance(String clsName, Configuration conf, |
| Properties props, ClassLoader loader) { |
| Object obj = newInstance(clsName, null, conf, loader, true); |
| configureInstance(obj, conf, props); |
| return obj; |
| } |
| |
| /** |
| * Loads the given class name by the given loader. |
| * For efficiency, a cache per class loader is maintained of classes already loader. |
| * @param clsName |
| * @param loader |
| */ |
| static Class<?> loadClass(String clsName, ClassLoader loader) { |
| Class<?> cls = null; |
| Object key = loader == null ? NULL_LOADER : loader; |
| Map<String,Class<?>> loaderCache = (Map<String,Class<?>>) _loaders.get(key); |
| if (loaderCache == null) { // We don't have a cache for this loader. |
| //OPENJPA-2636: Changed to HARD/WEAK to avoid Classloader leak: |
| loaderCache = new ConcurrentReferenceHashMap(ReferenceStrength.HARD, |
| ReferenceStrength.WEAK); |
| _loaders.put(key, loaderCache); |
| } else { // We have a cache for this loader. |
| cls = (Class<?>) loaderCache.get(clsName); |
| } |
| if (cls == null) { |
| try { |
| cls = ClassUtil.toClass(clsName, loader); |
| loaderCache.put(clsName, cls); |
| } catch (RuntimeException re) { |
| // TODO, empty block is never good |
| } |
| } |
| return cls; |
| } |
| |
| /** |
| * Helper method used by members of this package to instantiate plugin |
| * values. |
| */ |
| static Object newInstance(String clsName, Value val, Configuration conf, |
| ClassLoader loader, boolean fatal) { |
| if (StringUtil.isEmpty(clsName)) |
| return null; |
| |
| Class<?> cls = loadClass(clsName, findDerivedLoader(conf, loader)); |
| if (cls == null) { |
| cls = loadClass(clsName, findDerivedLoader(conf, null)); |
| } |
| if (cls == null && conf.getUserClassLoader() != null) { |
| cls = loadClass(clsName, conf.getUserClassLoader()); |
| } |
| |
| if (cls == null) { |
| if (fatal) |
| throw getCreateException(clsName, val, new ClassNotFoundException(clsName)); |
| Log log = (conf == null) ? null : conf.getConfigurationLog(); |
| if (log != null && log.isErrorEnabled()) |
| log.error(_loc.get("plugin-creation-exception", val)); |
| return null; |
| } |
| |
| try { |
| return AccessController.doPrivileged(J2DoPrivHelper.newInstanceAction(cls)); |
| } catch (Exception e) { |
| if (e instanceof PrivilegedActionException) { |
| e = ((PrivilegedActionException) e).getException(); |
| } |
| RuntimeException re = new RuntimeException(_loc.get("obj-create", cls).getMessage(), e); |
| if (fatal) |
| throw re; |
| Log log = (conf == null) ? null : conf.getConfigurationLog(); |
| if (log != null && log.isErrorEnabled()) |
| log.error(_loc.get("plugin-creation-exception", val), re); |
| return null; |
| } |
| } |
| |
| /** |
| * Attempt to find a derived loader that delegates to our target loader. |
| * This allows application loaders that delegate appropriately for known |
| * classes first crack at class names. |
| */ |
| private static ClassLoader findDerivedLoader(Configuration conf, ClassLoader loader) { |
| // we always prefer the thread loader, because it's the only thing we |
| // can access that isn't bound to the OpenJPA classloader, unless |
| // the conf object is of a custom class |
| ClassLoader ctxLoader = AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction()); |
| if (loader == null) { |
| if (ctxLoader != null) { |
| return ctxLoader; |
| } else if (conf != null) { |
| return classLoaderOf(conf.getClass()); |
| } else { |
| return classLoaderOf(Configurations.class); |
| } |
| } |
| |
| for (ClassLoader parent = ctxLoader; parent != null; parent = parentClassLoaderOf(parent)) { |
| if (parent == loader) |
| return ctxLoader; |
| } |
| if (conf != null) { |
| for (ClassLoader parent = classLoaderOf(conf.getClass()); parent != null; |
| parent = parentClassLoaderOf(parent)) { |
| if (parent == loader) |
| return classLoaderOf(conf.getClass()); |
| } |
| } |
| return loader; |
| } |
| |
| static ClassLoader classLoaderOf(Class<?> cls) { |
| return AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(cls)); |
| } |
| |
| static ClassLoader parentClassLoaderOf(ClassLoader loader) { |
| return AccessController.doPrivileged(J2DoPrivHelper.getParentAction(loader)); |
| } |
| |
| /** |
| * Return a List<String> of all the fully-qualified anchors specified in the |
| * properties location listed in <code>opts</code>. If no properties |
| * location is listed in <code>opts</code>, this returns whatever the |
| * product derivations can find in their default configurations. |
| * If the properties location specified in <code>opts</code> already |
| * contains an anchor spec, this returns that anchor. Note that in this |
| * fully-qualified-input case, the logic involving product derivations |
| * and resource parsing is short-circuited, so this method |
| * should not be used as a means to test that a particular anchor is |
| * defined in a given location by invoking with a fully-qualified anchor. |
| * |
| * This does not mutate <code>opts</code>. |
| * |
| * @since 1.1.0 |
| */ |
| public static List<String> getFullyQualifiedAnchorsInPropertiesLocation( |
| Options opts) { |
| String props = opts.getProperty("properties", "p", null); |
| if (props != null) { |
| int anchorPosition = props.indexOf("#"); |
| if (anchorPosition > -1) |
| return Arrays.asList(new String[] { props }); |
| } |
| |
| return ProductDerivations.getFullyQualifiedAnchorsInPropertiesLocation( |
| props); |
| } |
| |
| /** |
| * Set the given {@link Configuration} instance from the command line |
| * options provided. All property names of the given configuration are |
| * recognized; additionally, if a <code>properties</code> or |
| * <code>p</code> argument exists, the resource it |
| * points to will be loaded and set into the given configuration instance. |
| * It can point to either a file or a resource name. |
| */ |
| public static void populateConfiguration(Configuration conf, Options opts) { |
| String props = opts.removeProperty("properties", "p", null); |
| ConfigurationProvider provider; |
| if (!StringUtil.isEmpty(props)) { |
| Map<String, String> result = parseConfigResource(props); |
| String path = result.get(CONFIG_RESOURCE_PATH); |
| String anchor = result.get(CONFIG_RESOURCE_ANCHOR); |
| |
| File file = new File(path); |
| if ((AccessController.doPrivileged(J2DoPrivHelper |
| .isFileAction(file))).booleanValue()) |
| provider = ProductDerivations.load(file, anchor, null); |
| else { |
| file = new File("META-INF" + File.separatorChar + path); |
| if ((AccessController.doPrivileged(J2DoPrivHelper |
| .isFileAction(file))).booleanValue()) |
| provider = ProductDerivations.load(file, anchor, null); |
| else |
| provider = ProductDerivations.load(path, anchor, null); |
| } |
| if (provider != null) |
| provider.setInto(conf); |
| else |
| throw new MissingResourceException(_loc.get("no-provider", |
| props).getMessage(), Configurations.class.getName(), |
| props); |
| } else { |
| provider = ProductDerivations.loadDefaults(null); |
| if (provider != null) |
| provider.setInto(conf); |
| } |
| opts.setInto(conf); |
| } |
| |
| public static Map<String, String> parseConfigResource(String props) { |
| String path = props; |
| String anchor = null; |
| int idx = path.lastIndexOf('#'); |
| if (idx != -1) { |
| if (idx < path.length() - 1) |
| anchor = path.substring(idx + 1); |
| path = path.substring(0, idx); |
| if (path.length() == 0) |
| throw new MissingResourceException(_loc.get("anchor-only", |
| props).getMessage(), Configurations.class.getName(), |
| props); |
| } |
| Map <String, String> result = new HashMap<>(); |
| result.put(CONFIG_RESOURCE_PATH, path); |
| result.put(CONFIG_RESOURCE_ANCHOR, anchor); |
| return result; |
| } |
| |
| /** |
| * Helper method to throw an informative description on instantiation error. |
| */ |
| private static RuntimeException getCreateException(String clsName, Value val, Exception e) { |
| // re-throw the exception with some better information |
| final String msg; |
| final Object[] params; |
| |
| String alias = val.alias(clsName); |
| String[] aliases = val.getAliases(); |
| String[] keys; |
| if (aliases.length == 0) |
| keys = aliases; |
| else { |
| keys = new String[aliases.length / 2]; |
| for (int i = 0; i < aliases.length; i += 2) |
| keys[i / 2] = aliases[i]; |
| } |
| |
| String closest; |
| if (keys.length == 0) { |
| msg = "invalid-plugin"; |
| params = new Object[]{ val.getProperty(), alias, e.toString(), }; |
| } else if ((closest = StringDistance.getClosestLevenshteinDistance |
| (alias, keys, 0.5f)) == null) { |
| msg = "invalid-plugin-aliases"; |
| params = new Object[]{ |
| val.getProperty(), alias, e.toString(), |
| new TreeSet<>(Arrays.asList(keys)), }; |
| } else { |
| msg = "invalid-plugin-aliases-hint"; |
| params = new Object[]{ |
| val.getProperty(), alias, e.toString(), |
| new TreeSet<>(Arrays.asList(keys)), closest, }; |
| } |
| return new ParseException(_loc.get(msg, params), e); |
| } |
| |
| /** |
| * Configures the given object with the given properties by |
| * matching the properties string to the object's setter |
| * methods. The properties string should be in the form |
| * "prop1=val1, prop2=val2 ...". Does not validate that setter |
| * methods exist for the properties. |
| * |
| * @throws RuntimeException on configuration error |
| */ |
| public static void configureInstance(Object obj, Configuration conf, |
| String properties) { |
| configureInstance(obj, conf, properties, null); |
| } |
| |
| /** |
| * Configures the given object with the given properties by |
| * matching the properties string to the object's setter |
| * methods. The properties string should be in the form |
| * "prop1=val1, prop2=val2 ...". Validates that setter methods |
| * exist for the properties. |
| * |
| * @throws RuntimeException on configuration error |
| */ |
| public static void configureInstance(Object obj, Configuration conf, |
| String properties, String configurationName) { |
| if (obj == null) |
| return; |
| |
| Properties props = null; |
| if (!StringUtil.isEmpty(properties)) |
| props = parseProperties(properties); |
| configureInstance(obj, conf, props, configurationName); |
| } |
| |
| /** |
| * Configures the given object with the given properties by |
| * matching the properties string to the object's setter |
| * methods. Does not validate that setter methods exist for the properties. |
| * |
| * @throws RuntimeException on configuration error |
| */ |
| public static void configureInstance(Object obj, Configuration conf, |
| Properties properties) { |
| configureInstance(obj, conf, properties, null); |
| } |
| |
| /** |
| * Configures the given object with the given properties by |
| * matching the properties string to the object's setter |
| * methods. If <code>configurationName</code> is |
| * non-<code>null</code>, validates that setter methods exist for |
| * the properties. |
| * |
| * @throws RuntimeException on configuration error |
| */ |
| public static void configureInstance(Object obj, Configuration conf, |
| Properties properties, String configurationName) { |
| if (obj == null) |
| return; |
| |
| Options opts; |
| if (properties instanceof Options) |
| opts = (Options) properties; |
| else { |
| opts = new Options(); |
| if (properties != null) |
| opts.putAll(properties); |
| } |
| |
| Configurable configurable = null; |
| if (conf != null && obj instanceof Configurable) |
| configurable = (Configurable) obj; |
| |
| if (configurable != null) { |
| configurable.setConfiguration(conf); |
| configurable.startConfiguration(); |
| } |
| Options invalidEntries = opts.setInto(obj); |
| if (obj instanceof GenericConfigurable) |
| ((GenericConfigurable) obj).setInto(invalidEntries); |
| |
| if (!invalidEntries.isEmpty() && configurationName != null) { |
| Localizer.Message msg = null; |
| String first = (String) invalidEntries.keySet().iterator().next(); |
| if (invalidEntries.keySet().size() == 1 && |
| first.indexOf('.') == -1) { |
| // if there's just one misspelling and this is not a |
| // path traversal, check for near misses. |
| Collection<String> options = findOptionsFor(obj.getClass()); |
| String close = StringDistance.getClosestLevenshteinDistance |
| (first, options, 0.75f); |
| if (close != null) |
| msg = _loc.get("invalid-config-param-hint", new Object[]{ |
| configurationName, obj.getClass(), first, close, |
| options, }); |
| } |
| |
| if (msg == null) { |
| msg = _loc.get("invalid-config-params", new String[]{ |
| configurationName, obj.getClass().getName(), |
| invalidEntries.keySet().toString(), |
| findOptionsFor(obj.getClass()).toString(), }); |
| } |
| throw new ParseException(msg); |
| } |
| if (configurable != null) |
| configurable.endConfiguration(); |
| } |
| |
| private static Collection<String> findOptionsFor(Class<?> cls) { |
| Collection<String> c = Options.findOptionsFor(cls); |
| |
| // remove Configurable.setConfiguration() and |
| // GenericConfigurable.setInto() from the set, if applicable. |
| if (Configurable.class.isAssignableFrom(cls)) |
| c.remove("Configuration"); |
| if (GenericConfigurable.class.isAssignableFrom(cls)) |
| c.remove("Into"); |
| |
| return c; |
| } |
| |
| /** |
| * Turn a set of properties into a comma-separated string. |
| */ |
| public static String serializeProperties(Map map) { |
| if (map == null || map.isEmpty()) |
| return null; |
| |
| StringBuilder buf = new StringBuilder(); |
| Map.Entry entry; |
| String val; |
| for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next(); |
| if (buf.length() > 0) |
| buf.append(", "); |
| buf.append(entry.getKey()).append('='); |
| val = String.valueOf(entry.getValue()); |
| if (val.indexOf(',') != -1) |
| buf.append('"').append(val).append('"'); |
| else |
| buf.append(val); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Parse a set of properties from a comma-separated string. |
| */ |
| public static Options parseProperties(String properties) { |
| Options opts = new Options(); |
| properties = StringUtil.trimToNull(properties); |
| if (properties == null) |
| return opts; |
| |
| try { |
| String[] props = StringUtil.split(properties, ",", 0); |
| int idx; |
| char quote; |
| String prop; |
| String val; |
| for (int i = 0; i < props.length; i++) { |
| idx = props[i].indexOf('='); |
| if (idx == -1) { |
| // if the key is not assigned to any value, set the |
| // value to the same thing as the key, and continue. |
| // This permits GenericConfigurable instances to |
| // behave meaningfully. We might consider setting the |
| // value to some well-known "value was not set, but |
| // key is present" string so that instances getting |
| // values injected can differentiate between a mentioned |
| // property and one set to a particular value. |
| prop = props[i]; |
| val = prop; |
| } else { |
| prop = props[i].substring(0, idx).trim(); |
| val = props[i].substring(idx + 1).trim(); |
| } |
| |
| // if the value is quoted, read until the end quote |
| if (((val.startsWith("\"") && val.endsWith("\"")) |
| || (val.startsWith("'") && val.endsWith("'"))) |
| && val.length() > 1) |
| val = val.substring(1, val.length() - 1); |
| else if (val.startsWith("\"") || val.startsWith("'")) { |
| quote = val.charAt(0); |
| StringBuilder buf = new StringBuilder(val.substring(1)); |
| int quotIdx; |
| while (++i < props.length) { |
| buf.append(","); |
| |
| quotIdx = props[i].indexOf(quote); |
| if (quotIdx != -1) { |
| buf.append(props[i].substring(0, quotIdx)); |
| if (quotIdx + 1 < props[i].length()) |
| buf.append(props[i].substring(quotIdx + 1)); |
| break; |
| } else |
| buf.append(props[i]); |
| } |
| val = buf.toString(); |
| } |
| opts.put(prop, val); |
| } |
| return opts; |
| } catch (RuntimeException re) { |
| throw new ParseException(_loc.get("prop-parse", properties), re); |
| } |
| } |
| |
| /** |
| * Looks up the given name in JNDI. If the name is null, null is returned. |
| */ |
| public static Object lookup(String name, String userKey, Log log) { |
| if (StringUtil.isEmpty(name)) |
| return null; |
| |
| Context ctx = null; |
| try { |
| ctx = new InitialContext(); |
| Object result = ctx.lookup(name); |
| if (result == null && log != null && log.isWarnEnabled()) |
| log.warn(_loc.get("jndi-lookup-failed", userKey, name)); |
| return result; |
| } catch (NamingException ne) { |
| throw new RuntimeException( |
| _loc.get("naming-err", name).getMessage(), ne); |
| } finally { |
| if (ctx != null) { |
| try { |
| ctx.close(); |
| } catch (NamingException ne) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| /** |
| * Test whether the map contains the given partial key, prefixed with any |
| * possible configuration prefix. |
| */ |
| public static boolean containsProperty(Value value, Map props) { |
| if (value == null || props == null || props.isEmpty()) |
| return false; |
| List<String> partialKeys = value.getPropertyKeys(); |
| for (String partialKey : partialKeys) { |
| if (props.containsKey( |
| ProductDerivations.getConfigurationKey(partialKey, props))) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Test whether the map contains the given partial key, prefixed with any |
| * possible configuration prefix. |
| */ |
| public static boolean containsProperty(String partialKey, Map props) { |
| if (partialKey == null || props == null || props.isEmpty()) |
| return false; |
| else |
| return props.containsKey( |
| ProductDerivations.getConfigurationKey(partialKey, props)); |
| } |
| |
| /** |
| * Get the property under the given partial key, prefixed with any possible |
| * configuration prefix. |
| */ |
| public static Object getProperty(String partialKey, Map m) { |
| if (partialKey == null || m == null || m.isEmpty()) |
| return null; |
| else |
| return m.get(ProductDerivations.getConfigurationKey(partialKey, m)); |
| } |
| |
| /** |
| * Remove the property under the given partial key, prefixed with any |
| * possible configuration prefix. |
| */ |
| public static Object removeProperty(String partialKey, Map props) { |
| if (partialKey == null || props == null || props.isEmpty()) |
| return null; |
| if (containsProperty(partialKey, props)) |
| return props.remove(ProductDerivations.getConfigurationKey(partialKey, props)); |
| else |
| return null; |
| } |
| |
| public static void removeProperty(String partialKey, Map<?,?> remaining, Map<?,?> props) { |
| if (removeProperty(partialKey, remaining) != null) { |
| removeProperty(partialKey, props); |
| } |
| } |
| |
| /** |
| * Runs <code>runnable</code> against all the anchors in the configuration |
| * pointed to by <code>opts</code>. Each invocation gets a fresh clone of |
| * <code>opts</code> with the <code>properties</code> option set |
| * appropriately. |
| * |
| * @since 1.1.0 |
| */ |
| public static boolean runAgainstAllAnchors(Options opts, |
| Configurations.Runnable runnable) { |
| if (opts.containsKey("help") || opts.containsKey("-help")) { |
| return false; |
| } |
| List<String> anchors = |
| Configurations.getFullyQualifiedAnchorsInPropertiesLocation(opts); |
| |
| // We use 'properties' below; get rid of 'p' to avoid conflicts. This |
| // relies on knowing what getFullyQualifiedAnchorsInPropertiesLocation |
| // looks for. |
| if (opts.containsKey("p")) |
| opts.remove("p"); |
| |
| boolean ret = true; |
| if (anchors.size() == 0) { |
| ret = launchRunnable(opts, runnable); |
| } else { |
| for(String s : anchors ) { |
| Options clonedOptions = (Options) opts.clone(); |
| clonedOptions.setProperty("properties", s); |
| ret &= launchRunnable(clonedOptions, runnable); |
| } |
| } |
| return ret; |
| } |
| |
| private static boolean launchRunnable(Options opts, |
| Configurations.Runnable runnable) { |
| boolean ret = true; |
| try { |
| ret = runnable.run(opts); |
| } catch (Exception e) { |
| if (!(e instanceof RuntimeException)) |
| throw new RuntimeException(e); |
| else |
| throw (RuntimeException) e; |
| } |
| return ret; |
| } |
| |
| public interface Runnable { |
| boolean run(Options opts) throws Exception; |
| } |
| } |