JNDIConfiguration.java
- /*
- * 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 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");
- }
- }