    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.configuration;
    019
    020    import java.util.ArrayList;
    021    import java.util.HashSet;
    022    import java.util.Iterator;
    023    import java.util.List;
    024    import java.util.Set;
    025
    026    import javax.naming.Context;
    027    import javax.naming.InitialContext;
    028    import javax.naming.NameClassPair;
    029    import javax.naming.NameNotFoundException;
    030    import javax.naming.NamingEnumeration;
    031    import javax.naming.NamingException;
    032    import javax.naming.NotContextException;
    033
    034    import org.apache.commons.lang.StringUtils;
    035    import org.apache.commons.logging.LogFactory;
    036
    037    /**
    038     * This Configuration class allows you to interface with a JNDI datasource.
    039     * A JNDIConfiguration is read-only, write operations will throw an
    040     * UnsupportedOperationException. The clear operations are supported but the
    041     * underlying JNDI data source is not changed.
    042     *
    043     * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
    044     * @version $Id: JNDIConfiguration.java 1081926 2011-03-15 20:20:25Z oheger $
    045     */
    046    public class JNDIConfiguration extends AbstractConfiguration
    047    {
    048        /** The prefix of the context. */
    049        private String prefix;
    050
    051        /** The initial JNDI context. */
    052        private Context context;
    053
    054        /** The base JNDI context. */
    055        private Context baseContext;
    056
    057        /** The Set of keys that have been virtually cleared. */
    058        private Set clearedProperties = new HashSet();
    059
    060        /**
    061         * Creates a JNDIConfiguration using the default initial context as the
    062         * root of the properties.
    063         *
    064         * @throws NamingException thrown if an error occurs when initializing the default context
    065         */
    066        public JNDIConfiguration() throws NamingException
    067        {
    068            this((String) null);
    069        }
    070
    071        /**
    072         * Creates a JNDIConfiguration using the default initial context, shifted
    073         * with the specified prefix, as the root of the properties.
    074         *
    075         * @param prefix the prefix
    076         *
    077         * @throws NamingException thrown if an error occurs when initializing the default context
    078         */
    079        public JNDIConfiguration(String prefix) throws NamingException
    080        {
    081            this(new InitialContext(), prefix);
    082        }
    083
    084        /**
    085         * Creates a JNDIConfiguration using the specified initial context as the
    086         * root of the properties.
    087         *
    088         * @param context the initial context
    089         */
    090        public JNDIConfiguration(Context context)
    091        {
    092            this(context, null);
    093        }
    094
    095        /**
    096         * Creates a JNDIConfiguration using the specified initial context shifted
    097         * by the specified prefix as the root of the properties.
    098         *
    099         * @param context the initial context
    100         * @param prefix the prefix
    101         */
    102        public JNDIConfiguration(Context context, String prefix)
    103        {
    104            this.context = context;
    105            this.prefix = prefix;
    106            setLogger(LogFactory.getLog(getClass()));
    107            addErrorLogListener();
    108        }
    109
    110        /**
    111         * This method recursive traverse the JNDI tree, looking for Context objects.
    112         * When it finds them, it traverses them as well.  Otherwise it just adds the
    113         * values to the list of keys found.
    114         *
    115         * @param keys All the keys that have been found.
    116         * @param context The parent context
    117         * @param prefix What prefix we are building on.
    118         * @param processedCtx a set with the so far processed objects
    119         * @throws NamingException If JNDI has an issue.
    120         */
    121        private void recursiveGetKeys(Set keys, Context context, String prefix, Set processedCtx) throws NamingException
    122        {
    123            processedCtx.add(context);
    124            NamingEnumeration elements = null;
    125
    126            try
    127            {
    128                elements = context.list("");
    129
    130                // iterates through the context's elements
    131                while (elements.hasMore())
    132                {
    133                    NameClassPair nameClassPair = (NameClassPair) elements.next();
    134                    String name = nameClassPair.getName();
    135                    Object object = context.lookup(name);
    136
    137                    // build the key
    138                    StringBuffer key = new StringBuffer();
    139                    key.append(prefix);
    140                    if (key.length() > 0)
    141                    {
    142                        key.append(".");
    143                    }
    144                    key.append(name);
    145
    146                    if (object instanceof Context)
    147                    {
    148                        // add the keys of the sub context
    149                        Context subcontext = (Context) object;
    150                        if (!processedCtx.contains(subcontext))
    151                        {
    152                            recursiveGetKeys(keys, subcontext, key.toString(),
    153                                    processedCtx);
    154                        }
    155                    }
    156                    else
    157                    {
    158                        // add the key
    159                        keys.add(key.toString());
    160                    }
    161                }
    162            }
    163            finally
    164            {
    165                // close the enumeration
    166                if (elements != null)
    167                {
    168                    elements.close();
    169                }
    170            }
    171        }
    172
    173        /**
    174         * Returns an iterator with all property keys stored in this configuration.
    175         *
    176         * @return an iterator with all keys
    177         */
    178        public Iterator getKeys()
    179        {
    180            return getKeys("");
    181        }
    182
    183        /**
    184         * Returns an iterator with all property keys starting with the given
    185         * prefix.
    186         *
    187         * @param prefix the prefix
    188         * @return an iterator with the selected keys
    189         */
    190        public Iterator getKeys(String prefix)
    191        {
    192            // build the path
    193            String[] splitPath = StringUtils.split(prefix, ".");
    194
    195            List path = new ArrayList();
    196
    197            for (int i = 0; i < splitPath.length; i++)
    198            {
    199                path.add(splitPath[i]);
    200            }
    201
    202            try
    203            {
    204                // find the context matching the specified path
    205                Context context = getContext(path, getBaseContext());
    206
    207                // return all the keys under the context found
    208                Set keys = new HashSet();
    209                if (context != null)
    210                {
    211                    recursiveGetKeys(keys, context, prefix, new HashSet());
    212                }
    213                else if (containsKey(prefix))
    214                {
    215                    // add the prefix if it matches exactly a property key
    216                    keys.add(prefix);
    217                }
    218
    219                return keys.iterator();
    220            }
    221            catch (NameNotFoundException e)
    222            {
    223                // expected exception, no need to log it
    224                return new ArrayList().iterator();
    225            }
    226            catch (NamingException e)
    227            {
    228                fireError(EVENT_READ_PROPERTY, null, null, e);
    229                return new ArrayList().iterator();
    230            }
    231        }
    232
    233        /**
    234         * Because JNDI is based on a tree configuration, we need to filter down the
    235         * tree, till we find the Context specified by the key to start from.
    236         * Otherwise return null.
    237         *
    238         * @param path     the path of keys to traverse in order to find the context
    239         * @param context  the context to start from
    240         * @return The context at that key's location in the JNDI tree, or null if not found
    241         * @throws NamingException if JNDI has an issue
    242         */
    243        private Context getContext(List path, Context context) throws NamingException
    244        {
    245            // return the current context if the path is empty
    246            if (path == null || path.isEmpty())
    247            {
    248                return context;
    249            }
    250
    251            String key = (String) path.get(0);
    252
    253            // search a context matching the key in the context's elements
    254            NamingEnumeration elements = null;
    255
    256            try
    257            {
    258                elements = context.list("");
    259                while (elements.hasMore())
    260                {
    261                    NameClassPair nameClassPair = (NameClassPair) elements.next();
    262                    String name = nameClassPair.getName();
    263                    Object object = context.lookup(name);
    264
    265                    if (object instanceof Context && name.equals(key))
    266                    {
    267                        Context subcontext = (Context) object;
    268
    269                        // recursive search in the sub context
    270                        return getContext(path.subList(1, path.size()), subcontext);
    271                    }
    272                }
    273            }
    274            finally
    275            {
    276                if (elements != null)
    277                {
    278                    elements.close();
    279                }
    280            }
    281
    282            return null;
    283        }
    284
    285        /**
    286         * Returns a flag whether this configuration is empty.
    287         *
    288         * @return the empty flag
    289         */
    290        public boolean isEmpty()
    291        {
    292            try
    293            {
    294                NamingEnumeration enumeration = null;
    295
    296                try
    297                {
    298                    enumeration = getBaseContext().list("");
    299                    return !enumeration.hasMore();
    300                }
    301                finally
    302                {
    303                    // close the enumeration
    304                    if (enumeration != null)
    305                    {
    306                        enumeration.close();
    307                    }
    308                }
    309            }
    310            catch (NamingException e)
    311            {
    312                fireError(EVENT_READ_PROPERTY, null, null, e);
    313                return true;
    314            }
    315        }
    316
    317        /**
    318         * <p><strong>This operation is not supported and will throw an
    319         * UnsupportedOperationException.</strong></p>
    320         *
    321         * @param key the key
    322         * @param value the value
    323         * @throws UnsupportedOperationException
    324         */
    325        public void setProperty(String key, Object value)
    326        {
    327            throw new UnsupportedOperationException("This operation is not supported");
    328        }
    329
    330        /**
    331         * Removes the specified property.
    332         *
    333         * @param key the key of the property to remove
    334         */
    335        public void clearProperty(String key)
    336        {
    337            clearedProperties.add(key);
    338        }
    339
    340        /**
    341         * Checks whether the specified key is contained in this configuration.
    342         *
    343         * @param key the key to check
    344         * @return a flag whether this key is stored in this configuration
    345         */
    346        public boolean containsKey(String key)
    347        {
    348            if (clearedProperties.contains(key))
    349            {
    350                return false;
    351            }
    352            key = StringUtils.replace(key, ".", "/");
    353            try
    354            {
    355                // throws a NamingException if JNDI doesn't contain the key.
    356                getBaseContext().lookup(key);
    357                return true;
    358            }
    359            catch (NameNotFoundException e)
    360            {
    361                // expected exception, no need to log it
    362                return false;
    363            }
    364            catch (NamingException e)
    365            {
    366                fireError(EVENT_READ_PROPERTY, key, null, e);
    367                return false;
    368            }
    369        }
    370
    371        /**
    372         * Returns the prefix.
    373         * @return the prefix
    374         */
    375        public String getPrefix()
    376        {
    377            return prefix;
    378        }
    379
    380        /**
    381         * Sets the prefix.
    382         *
    383         * @param prefix The prefix to set
    384         */
    385        public void setPrefix(String prefix)
    386        {
    387            this.prefix = prefix;
    388
    389            // clear the previous baseContext
    390            baseContext = null;
    391        }
    392
    393        /**
    394         * Returns the value of the specified property.
    395         *
    396         * @param key the key of the property
    397         * @return the value of this property
    398         */
    399        public Object getProperty(String key)
    400        {
    401            if (clearedProperties.contains(key))
    402            {
    403                return null;
    404            }
    405
    406            try
    407            {
    408                key = StringUtils.replace(key, ".", "/");
    409                return getBaseContext().lookup(key);
    410            }
    411            catch (NameNotFoundException e)
    412            {
    413                // expected exception, no need to log it
    414                return null;
    415            }
    416            catch (NotContextException nctxex)
    417            {
    418                // expected exception, no need to log it
    419                return null;
    420            }
    421            catch (NamingException e)
    422            {
    423                fireError(EVENT_READ_PROPERTY, key, null, e);
    424                return null;
    425            }
    426        }
    427
    428        /**
    429         * <p><strong>This operation is not supported and will throw an
    430         * UnsupportedOperationException.</strong></p>
    431         *
    432         * @param key the key
    433         * @param obj the value
    434         * @throws UnsupportedOperationException
    435         */
    436        protected void addPropertyDirect(String key, Object obj)
    437        {
    438            throw new UnsupportedOperationException("This operation is not supported");
    439        }
    440
    441        /**
    442         * Return the base context with the prefix applied.
    443         *
    444         * @return the base context
    445         * @throws NamingException if an error occurs
    446         */
    447        public Context getBaseContext() throws NamingException
    448        {
    449            if (baseContext == null)
    450            {
    451                baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix);
    452            }
    453
    454            return baseContext;
    455        }
    456
    457        /**
    458         * Return the initial context used by this configuration. This context is
    459         * independent of the prefix specified.
    460         *
    461         * @return the initial context
    462         */
    463        public Context getContext()
    464        {
    465            return context;
    466        }
    467
    468        /**
    469         * Set the initial context of the configuration.
    470         *
    471         * @param context the context
    472         */
    473        public void setContext(Context context)
    474        {
    475            // forget the removed properties
    476            clearedProperties.clear();
    477
    478            // change the context
    479            this.context = context;
    480        }
    481    }