Hmm, navigating through a tree based on a configuration key, creating missing nodes if necessary and finally storing the value...
Similar code is contained in HierarchicalConfiguration and I suppose in XMLConfiguration, too, to update the internally used DOM tree. I wonder if this could be generalized.
I've been pondering the same thing for quite some time too. I started working on a WindowsConfiguration wrapping the windows registry and I'm reimplementing the same logic found in the other hierarchical configurations. And we will have the same issue if we write a LDAPConfiguration.
I tried a quick refactoring of JNDIConfiguration to test this idea, this is a rough design, let me know what do you think of it :
- a ConfigurationNode interface is introduced, it's an abstract node with basic navigation methods like getElements(), createNode(), getName(). Its implementations will wrap a real node, that's a JNDI Context or a DOM Element for example.
- JNDIConfiguration implements a TreeConfiguration interface. This interface exposes the getRootNode() method returning the root ConfigurationNode. We may put in this interface some methods defining the variations of behaviour between the hierarchical configurations (supportsXXX():boolean). For example with an XMLConfiguration, several child nodes can have the same name, this is not true for JNDIConfiguration or WindowsConfiguration, that means the search algorithm is slightly different.
- JNDIConfiguration has a private implementation of ConfigurationNode, that's JNDIConfigurationNode.
- the search methods of JNDIConfiguration have been moved to TreeConfigurationUtils and now use the ConfigurationNode instead of the JNDI context.
- JNDIConfiguration delegates to TreeConfigurationUtils its implementations for getKeys and isEmpty.
I'm attaching the resulting classes, the test cases still pass. I started looking at XMLConfiguration but it's a bit more complex.
Emmanuel Bourg
/* * Copyright 2004 The Apache Software Foundation. * * Licensed 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.commons.configuration;
import java.util.Collection;
/**
* An abstract node for hierarchical configurations.
*
* @author Emmanuel Bourg
* @version $Revision$, $Date$
*/
public interface ConfigurationNode
{
String getName();
/**
* Return the elements under this node (both sub nodes and objects)
*/
Collection getElements();
//ConfigurationNode getNode(String name);
//void createNode(String name);
//void destroyNode(String name);
}
/* * Copyright 2004 The Apache Software Foundation. * * Licensed 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.commons.configuration; /** * Configuration based on a hierarchical data structure. * * @author Emmanuel Bourg * @version $Revision$, $Date$ */ public interface TreeConfiguration extends Configuration { ConfigurationNode getRootNode(); //boolean supportsNonUniqueNodes(); //boolean supportsDirectNodeLookup(); }
/* * Copyright 2004 The Apache Software Foundation. * * Licensed 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.commons.configuration; /** * A configuration property, that's a pair key/value. * * @author Emmanuel Bourg * @version $Revision$, $Date$ */ public class ConfigurationProperty { private String key; private Object value; public ConfigurationProperty(String key, Object value) { this.key = key; this.value = value; } public String getKey() { return key; } public Object getValue() { return value; } }
/* * Copyright 2001-2004 The Apache Software Foundation. * * Licensed 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.commons.configuration; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.Set; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameClassPair; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This Configuration class allows you to interface with a JNDI datasource. * A JNDIConfiguration is read-only, write operations will throw an * UnsupportedOperationException. The clear operations are supported but the * underlying JNDI data source is not changed. * * @author <a href="mailto:[EMAIL PROTECTED]">Eric Pugh</a> * @version $Id: JNDIConfiguration.java,v 1.20 2004/10/21 18:36:14 ebourg Exp $ */ public class JNDIConfiguration extends AbstractConfiguration implements TreeConfiguration { /** Logger. */ private static Log log = LogFactory.getLog(JNDIConfiguration.class); /** The prefix of the context. */ private String prefix; /** The initial JNDI context. */ private Context context; /** The base JNDI context. */ private Context baseContext; /** The Set of keys that have been virtually cleared. */ private Set clearedProperties = new HashSet(); /** * Creates a JNDIConfiguration using the default initial context as the * root of the properties. * * @throws NamingException thrown if an error occurs when initializing the default context */ public JNDIConfiguration() throws NamingException { this((String) null); } /** * Creates a JNDIConfiguration using the default initial context, shifted * with the specified prefix, as the root of the properties. * * @param prefix * * @throws NamingException thrown if an error occurs when initializing the default context */ public JNDIConfiguration(String prefix) throws NamingException { this(new InitialContext(), prefix); } /** * Creates a JNDIConfiguration using the specified initial context as the * root of the properties. * * @param context the initial context */ public JNDIConfiguration(Context context) { this(context, null); } /** * Creates a JNDIConfiguration using the specified initial context shifted * by the specified prefix as the root of the properties. * * @param context the initial context * @param prefix */ public JNDIConfiguration(Context context, String prefix) { this.context = context; this.prefix = prefix; } /** * <p><strong>This operation is not supported and will throw an * UnsupportedOperationException.</strong></p> * * @throws UnsupportedOperationException */ public void addProperty(String key, Object token) { throw new UnsupportedOperationException("This operation is not supported"); } /** * [EMAIL PROTECTED] */ public Iterator getKeys() { return getKeys(""); } /** * [EMAIL PROTECTED] */ public Iterator getKeys(String prefix) { return TreeConfigurationUtils.getKeys(this, prefix); } /** * <p><strong>This operation is not supported and will throw an * UnsupportedOperationException.</strong></p> * * @throws UnsupportedOperationException */ public Properties getProperties(String key) { throw new UnsupportedOperationException("This operation is not supported"); } /** * [EMAIL PROTECTED] */ public boolean isEmpty() { return TreeConfigurationUtils.isEmpty(this); } /** * [EMAIL PROTECTED] */ public Object getProperty(String key) { return getPropertyDirect(key); } /** * <p><strong>This operation is not supported and will throw an * UnsupportedOperationException.</strong></p> * * @throws UnsupportedOperationException */ public void setProperty(String key, Object value) { throw new UnsupportedOperationException("This operation is not supported"); } /** * [EMAIL PROTECTED] */ public void clearProperty(String key) { clearedProperties.add(key); } /** * [EMAIL PROTECTED] */ public boolean containsKey(String key) { if (clearedProperties.contains(key)) { return false; } key = StringUtils.replace(key, ".", "/"); try { // throws a NamingException if JNDI doesn't contain the key. getBaseContext().lookup(key); return true; } catch (NamingException e) { log.error(e.getMessage(), e); return false; } } /** * @return String */ public String getPrefix() { return prefix; } /** * Sets the prefix. * * @param prefix The prefix to set */ public void setPrefix(String prefix) { this.prefix = prefix; // clear the previous baseContext baseContext = null; } /** * [EMAIL PROTECTED] */ protected Object getPropertyDirect(String key) { if (clearedProperties.contains(key)) { return null; } try { key = StringUtils.replace(key, ".", "/"); return getBaseContext().lookup(key); } catch (NamingException e) { log.error(e.getMessage(), e); return null; } } /** * <p><strong>This operation is not supported and will throw an * UnsupportedOperationException.</strong></p> * * @throws UnsupportedOperationException */ protected void addPropertyDirect(String key, Object obj) { throw new UnsupportedOperationException("This operation is not supported"); } /** * Return the base context with the prefix applied. */ public Context getBaseContext() throws NamingException { if (baseContext == null) { baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix); } return baseContext; } /** * Return the initial context used by this configuration. This context is * independent of the prefix specified. */ public Context getContext() { return context; } /** * Set the initial context of the configuration. */ public void setContext(Context context) { // forget the removed properties clearedProperties.clear(); // change the context this.context = context; } public ConfigurationNode getRootNode() { ConfigurationNode root = null; try { root = new JNDIConfigurationNode(null, getBaseContext()); } catch (NamingException e) { log.error(e.getMessage(), e); } return root; } /** * A ConfigurationNode wrapping a JNDI Context */ private class JNDIConfigurationNode implements ConfigurationNode { private String name; private Context context; public JNDIConfigurationNode(String name, Context context) { this.name = name; this.context = context; } public String getName() { return name; } public Collection getElements() { Collection elements = new ArrayList(); try { NamingEnumeration objects = context.list(""); // todo close the enumeration while (objects.hasMore()) { NameClassPair nameClassPair = (NameClassPair) objects.next(); String name = nameClassPair.getName(); Object object = context.lookup(name); if (object instanceof Context) { elements.add(new JNDIConfigurationNode(name, (Context) object)); } else { elements.add(new ConfigurationProperty(name, object)); } } } catch (NamingException e) { log.error(e.getMessage(), e); } return elements; } } }
/* * Copyright 2004 The Apache Software Foundation. * * Licensed 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.commons.configuration; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.lang.StringUtils; /** * Utility methods for configurations implementing the TreeConfiguration interface. * * @author Emmanuel Bourg * @version $Revision$, $Date$ */ public class TreeConfigurationUtils { /** * Because JNDI is based on a tree configuration, we need to filter down the * tree, till we find the Context specified by the key to start from. * Otherwise return null. * * @param path the path of keys to traverse in order to find the context * @param node the node to start from * @return The context at that key's location in the JNDI tree, or null if not found */ public static ConfigurationNode getNode(List path, ConfigurationNode node) { // return the current node if the path is empty if (path == null || path.isEmpty()) { return node; } String key = (String) path.get(0); // search a node matching the key in the subnodes Iterator elements = node.getElements().iterator(); while (elements.hasNext()) { Object object = elements.next(); if (object instanceof ConfigurationNode) { ConfigurationNode subnode = (ConfigurationNode) object; if (subnode.getName().equals(key)) { // recursive search in the sub node return getNode(path.subList(1, path.size()), subnode); } } } return null; } public static Iterator getKeys(TreeConfiguration config, String prefix) { // build the path String[] splitPath = StringUtils.split(prefix, "."); List path = new ArrayList(); for (int i = 0; i < splitPath.length; i++) { path.add(splitPath[i]); } // find the node matching the specified path ConfigurationNode node = TreeConfigurationUtils.getNode(path, config.getRootNode()); // return all the keys under the node found Set keys = new HashSet(); if (node != null) { recursiveGetKeys(keys, node, prefix); } else if (config.containsKey(prefix)) { // add the prefix if it matches exactly a property key keys.add(prefix); } return keys.iterator(); } /** * This method recursive traverse the JNDI tree, looking for Context objects. * When it finds them, it traverses them as well. Otherwise it just adds the * values to the list of keys found. * * @param keys All the keys that have been found. * @param node The parent context * @param prefix What prefix we are building on. */ private static void recursiveGetKeys(Set keys, ConfigurationNode node, String prefix) { Iterator elements = node.getElements().iterator(); // iterates through the node's elements while (elements.hasNext()) { Object object = elements.next(); // build the key StringBuffer key = new StringBuffer(); key.append(prefix); if (key.length() > 0) { key.append("."); } if (object instanceof ConfigurationNode) { // add the keys of the sub node ConfigurationNode subnode = (ConfigurationNode) object; key.append(subnode.getName()); recursiveGetKeys(keys, subnode, key.toString()); } else { // add the key ConfigurationProperty property = (ConfigurationProperty) object; key.append(property.getKey()); keys.add(key.toString()); } } } public static boolean isEmpty(TreeConfiguration config) { Collection elements = config.getRootNode().getElements(); return elements.isEmpty(); } }
smime.p7s
Description: S/MIME Cryptographic Signature
