| /* |
| * 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 |
| * |
| * https://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 java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| 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 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 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(); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * <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"); |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * Tests whether this configuration contains one or more matches to this value. This operation stops at first match |
| * but may be more expensive than the containsKey method. |
| * |
| * @since 2.11.0 |
| */ |
| @Override |
| protected boolean containsValueInternal(final Object value) { |
| return contains(getKeys(), value); |
| } |
| |
| /** |
| * Gets 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; |
| } |
| |
| /** |
| * Gets the initial context used by this configuration. This context is independent of the prefix specified. |
| * |
| * @return the initial context |
| */ |
| public Context getContext() { |
| return context; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Gets an iterator with all property keys stored in this configuration. |
| * |
| * @return an iterator with all keys |
| */ |
| @Override |
| protected Iterator<String> getKeysInternal() { |
| return getKeysInternal(""); |
| } |
| |
| /** |
| * Gets 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, DELIMITER); |
| |
| 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(); |
| } |
| } |
| |
| /** |
| * Gets the prefix. |
| * |
| * @return the prefix |
| */ |
| public String getPrefix() { |
| return prefix; |
| } |
| |
| /** |
| * Gets 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; |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * 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(DELIMITER); |
| } |
| 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(); |
| } |
| } |
| } |
| |
| /** |
| * Sets 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; |
| } |
| |
| /** |
| * Sets the prefix. |
| * |
| * @param prefix The prefix to set |
| */ |
| public void setPrefix(final String prefix) { |
| this.prefix = prefix; |
| |
| // clear the previous baseContext |
| baseContext = null; |
| } |
| |
| /** |
| * <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"); |
| } |
| } |