| /* |
| * 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.commons.configuration2; |
| |
| import javax.naming.Context; |
| import javax.naming.InitialContext; |
| import javax.naming.NameClassPair; |
| import javax.naming.NameNotFoundException; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.NamingException; |
| import javax.naming.NotContextException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.commons.configuration2.event.ConfigurationErrorEvent; |
| import org.apache.commons.configuration2.io.ConfigurationLogger; |
| import org.apache.commons.lang3.StringUtils; |
| |
| /** |
| * 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. |
| * |
| */ |
| public class JNDIConfiguration extends AbstractConfiguration |
| { |
| /** 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 final Set<String> 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 the prefix |
| * |
| * @throws NamingException thrown if an error occurs when initializing the default context |
| */ |
| public JNDIConfiguration(final 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(final 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 the prefix |
| */ |
| public JNDIConfiguration(final Context context, final String prefix) |
| { |
| this.context = context; |
| this.prefix = prefix; |
| initLogger(new ConfigurationLogger(JNDIConfiguration.class)); |
| addErrorLogListener(); |
| } |
| |
| /** |
| * 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 context The parent context |
| * @param prefix What prefix we are building on. |
| * @param processedCtx a set with the so far processed objects |
| * @throws NamingException If JNDI has an issue. |
| */ |
| private void recursiveGetKeys(final Set<String> keys, final Context context, final String prefix, |
| final Set<Context> processedCtx) throws NamingException |
| { |
| processedCtx.add(context); |
| NamingEnumeration<NameClassPair> elements = null; |
| |
| try |
| { |
| elements = context.list(""); |
| |
| // iterates through the context's elements |
| while (elements.hasMore()) |
| { |
| final NameClassPair nameClassPair = elements.next(); |
| final String name = nameClassPair.getName(); |
| final Object object = context.lookup(name); |
| |
| // build the key |
| final StringBuilder keyBuilder = new StringBuilder(); |
| keyBuilder.append(prefix); |
| if (keyBuilder.length() > 0) |
| { |
| keyBuilder.append("."); |
| } |
| keyBuilder.append(name); |
| |
| if (object instanceof Context) |
| { |
| // add the keys of the sub context |
| final Context subcontext = (Context) object; |
| if (!processedCtx.contains(subcontext)) |
| { |
| recursiveGetKeys(keys, subcontext, keyBuilder.toString(), |
| processedCtx); |
| } |
| } |
| else |
| { |
| // add the key |
| keys.add(keyBuilder.toString()); |
| } |
| } |
| } |
| finally |
| { |
| // close the enumeration |
| if (elements != null) |
| { |
| elements.close(); |
| } |
| } |
| } |
| |
| /** |
| * Returns an iterator with all property keys stored in this configuration. |
| * |
| * @return an iterator with all keys |
| */ |
| @Override |
| protected Iterator<String> getKeysInternal() |
| { |
| return getKeysInternal(""); |
| } |
| |
| /** |
| * Returns an iterator with all property keys starting with the given |
| * prefix. |
| * |
| * @param prefix the prefix |
| * @return an iterator with the selected keys |
| */ |
| @Override |
| protected Iterator<String> getKeysInternal(final String prefix) |
| { |
| // build the path |
| final String[] splitPath = StringUtils.split(prefix, "."); |
| |
| final List<String> path = Arrays.asList(splitPath); |
| |
| try |
| { |
| // find the context matching the specified path |
| final Context context = getContext(path, getBaseContext()); |
| |
| // return all the keys under the context found |
| final Set<String> keys = new HashSet<>(); |
| if (context != null) |
| { |
| recursiveGetKeys(keys, context, prefix, new HashSet<>()); |
| } |
| else if (containsKey(prefix)) |
| { |
| // add the prefix if it matches exactly a property key |
| keys.add(prefix); |
| } |
| |
| return keys.iterator(); |
| } |
| catch (final NameNotFoundException e) |
| { |
| // expected exception, no need to log it |
| return new ArrayList<String>().iterator(); |
| } |
| catch (final NamingException e) |
| { |
| fireError(ConfigurationErrorEvent.READ, |
| ConfigurationErrorEvent.READ, null, null, e); |
| return new ArrayList<String>().iterator(); |
| } |
| } |
| |
| /** |
| * 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 context the context to start from |
| * @return The context at that key's location in the JNDI tree, or null if not found |
| * @throws NamingException if JNDI has an issue |
| */ |
| private Context getContext(final List<String> path, final Context context) throws NamingException |
| { |
| // return the current context if the path is empty |
| if (path == null || path.isEmpty()) |
| { |
| return context; |
| } |
| |
| final String key = path.get(0); |
| |
| // search a context matching the key in the context's elements |
| NamingEnumeration<NameClassPair> elements = null; |
| |
| try |
| { |
| elements = context.list(""); |
| while (elements.hasMore()) |
| { |
| final NameClassPair nameClassPair = elements.next(); |
| final String name = nameClassPair.getName(); |
| final Object object = context.lookup(name); |
| |
| if (object instanceof Context && name.equals(key)) |
| { |
| final Context subcontext = (Context) object; |
| |
| // recursive search in the sub context |
| return getContext(path.subList(1, path.size()), subcontext); |
| } |
| } |
| } |
| finally |
| { |
| if (elements != null) |
| { |
| elements.close(); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns a flag whether this configuration is empty. |
| * |
| * @return the empty flag |
| */ |
| @Override |
| protected boolean isEmptyInternal() |
| { |
| try |
| { |
| NamingEnumeration<NameClassPair> enumeration = null; |
| |
| try |
| { |
| enumeration = getBaseContext().list(""); |
| return !enumeration.hasMore(); |
| } |
| finally |
| { |
| // close the enumeration |
| if (enumeration != null) |
| { |
| enumeration.close(); |
| } |
| } |
| } |
| catch (final NamingException e) |
| { |
| fireError(ConfigurationErrorEvent.READ, |
| ConfigurationErrorEvent.READ, null, null, e); |
| return true; |
| } |
| } |
| |
| /** |
| * <p><strong>This operation is not supported and will throw an |
| * UnsupportedOperationException.</strong></p> |
| * |
| * @param key the key |
| * @param value the value |
| * @throws UnsupportedOperationException always thrown as this method is not supported |
| */ |
| @Override |
| protected void setPropertyInternal(final String key, final Object value) |
| { |
| throw new UnsupportedOperationException("This operation is not supported"); |
| } |
| |
| /** |
| * Removes the specified property. |
| * |
| * @param key the key of the property to remove |
| */ |
| @Override |
| protected void clearPropertyDirect(final String key) |
| { |
| clearedProperties.add(key); |
| } |
| |
| /** |
| * Checks whether the specified key is contained in this configuration. |
| * |
| * @param key the key to check |
| * @return a flag whether this key is stored in this configuration |
| */ |
| @Override |
| protected boolean containsKeyInternal(String key) |
| { |
| if (clearedProperties.contains(key)) |
| { |
| return false; |
| } |
| key = key.replace('.', '/'); |
| try |
| { |
| // throws a NamingException if JNDI doesn't contain the key. |
| getBaseContext().lookup(key); |
| return true; |
| } |
| catch (final NameNotFoundException e) |
| { |
| // expected exception, no need to log it |
| return false; |
| } |
| catch (final NamingException e) |
| { |
| fireError(ConfigurationErrorEvent.READ, |
| ConfigurationErrorEvent.READ, key, null, e); |
| return false; |
| } |
| } |
| |
| /** |
| * Returns the prefix. |
| * @return the prefix |
| */ |
| public String getPrefix() |
| { |
| return prefix; |
| } |
| |
| /** |
| * Sets the prefix. |
| * |
| * @param prefix The prefix to set |
| */ |
| public void setPrefix(final String prefix) |
| { |
| this.prefix = prefix; |
| |
| // clear the previous baseContext |
| baseContext = null; |
| } |
| |
| /** |
| * Returns the value of the specified property. |
| * |
| * @param key the key of the property |
| * @return the value of this property |
| */ |
| @Override |
| protected Object getPropertyInternal(String key) |
| { |
| if (clearedProperties.contains(key)) |
| { |
| return null; |
| } |
| |
| try |
| { |
| key = key.replace('.', '/'); |
| return getBaseContext().lookup(key); |
| } |
| catch (final NameNotFoundException | NotContextException nctxex) |
| { |
| // expected exception, no need to log it |
| return null; |
| } |
| catch (final NamingException e) |
| { |
| fireError(ConfigurationErrorEvent.READ, |
| ConfigurationErrorEvent.READ, key, null, e); |
| return null; |
| } |
| } |
| |
| /** |
| * <p><strong>This operation is not supported and will throw an |
| * UnsupportedOperationException.</strong></p> |
| * |
| * @param key the key |
| * @param obj the value |
| * @throws UnsupportedOperationException always thrown as this method is not supported |
| */ |
| @Override |
| protected void addPropertyDirect(final String key, final Object obj) |
| { |
| throw new UnsupportedOperationException("This operation is not supported"); |
| } |
| |
| /** |
| * Return the base context with the prefix applied. |
| * |
| * @return the base context |
| * @throws NamingException if an error occurs |
| */ |
| 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. |
| * |
| * @return the initial context |
| */ |
| public Context getContext() |
| { |
| return context; |
| } |
| |
| /** |
| * Set the initial context of the configuration. |
| * |
| * @param context the context |
| */ |
| public void setContext(final Context context) |
| { |
| // forget the removed properties |
| clearedProperties.clear(); |
| |
| // change the context |
| this.context = context; |
| } |
| } |