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    package org.apache.commons.configuration;
018    
019    import java.util.Hashtable;
020    
021    import javax.naming.Context;
022    import javax.naming.NameClassPair;
023    import javax.naming.NameNotFoundException;
024    import javax.naming.NamingEnumeration;
025    import javax.naming.NamingException;
026    import javax.naming.spi.InitialContextFactory;
027    
028    import com.mockobjects.dynamic.C;
029    import com.mockobjects.dynamic.Mock;
030    
031    /**
032     * A mock implementation of the {@code InitialContextFactory} interface.
033     * This implementation will return a mock context that contains some test data.
034     *
035     * @author <a
036     * href="http://commons.apache.org/configuration/team-list.html">Commons
037     * Configuration team</a>
038     * @version $Id: MockInitialContextFactory.java 1222455 2011-12-22 21:10:10Z oheger $
039     */
040    public class MockInitialContextFactory implements InitialContextFactory
041    {
042        /**
043         * Constant for the use cycles environment property. If this property is
044         * present in the environment, a cyclic context will be created.
045         */
046        public static final String PROP_CYCLES = "useCycles";
047    
048        /** Constant for the lookup method. */
049        private static final String METHOD_LOOKUP = "lookup";
050    
051        /** Constant for the list method. */
052        private static final String METHOD_LIST = "list";
053    
054        /** Constant for the close method.*/
055        private static final String METHOD_CLOSE = "close";
056    
057        /** Constant for the name of the missing property. */
058        private static final String MISSING_PROP = "/missing";
059    
060        /** Constant for the name of the prefix. */
061        private static final String PREFIX = "test/";
062    
063        /** An array with the names of the supported properties. */
064        private static final String[] PROP_NAMES =
065        { "key", "key2", "short", "boolean", "byte", "double", "float", "integer",
066                "long", "onlyinjndi" };
067    
068        /** An array with the values of the supported properties. */
069        private static final String[] PROP_VALUES =
070        { "jndivalue", "jndivalue2", "1", "true", "10", "10.25", "20.25", "10",
071                "1000000", "true" };
072    
073        /** An array with properties that are requested, but are not in the context. */
074        private static final String[] MISSING_NAMES =
075        { "missing/list", "test/imaginarykey", "foo/bar" };
076    
077        /**
078         * Creates a {@code Context} object that is backed by a mock object.
079         * The mock context can be queried for the values of certain test
080         * properties. It also supports listing the contained (sub) properties.
081         *
082         * @param env the environment
083         * @return the context mock
084         */
085        public Context getInitialContext(@SuppressWarnings("rawtypes") Hashtable env) throws NamingException
086        {
087            boolean useCycles = env.containsKey(PROP_CYCLES);
088    
089            Mock mockTopCtx = createCtxMock(PREFIX);
090            Mock mockCycleCtx = createCtxMock("");
091            Mock mockPrfxCtx = createCtxMock("");
092            Mock mockBaseCtx = new Mock(Context.class);
093            mockBaseCtx.matchAndReturn(METHOD_LOOKUP, C.eq(""), mockTopCtx.proxy());
094            mockBaseCtx.matchAndReturn(METHOD_LOOKUP, C.eq("test"), mockPrfxCtx
095                    .proxy());
096            mockTopCtx.matchAndReturn(METHOD_LOOKUP, C.eq("test"), mockPrfxCtx
097                    .proxy());
098            mockPrfxCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
099                    mockPrfxCtx, PROP_NAMES, PROP_VALUES).proxy());
100    
101            if (useCycles)
102            {
103                mockTopCtx.matchAndReturn(METHOD_LOOKUP, C.eq("cycle"),
104                        mockCycleCtx.proxy());
105                mockTopCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
106                        mockTopCtx, new String[]
107                        { "test", "cycle" }, new Object[]
108                        { mockPrfxCtx.proxy(), mockCycleCtx.proxy() }).proxy());
109                Mock mockEnum = createEnumMock(mockCycleCtx, PROP_NAMES,
110                        PROP_VALUES, false);
111                addEnumPair(mockEnum, "cycleCtx", mockCycleCtx.proxy());
112                closeEnum(mockEnum);
113                mockCycleCtx
114                        .matchAndReturn(METHOD_LIST, C.eq(""), mockEnum.proxy());
115                mockCycleCtx.matchAndReturn(METHOD_LOOKUP, C.eq("cycleCtx"),
116                        mockCycleCtx.proxy());
117            }
118            else
119            {
120                mockTopCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
121                        mockTopCtx, new String[]
122                        { "test" }, new Object[]
123                        { mockPrfxCtx.proxy() }).proxy());
124            }
125            return (Context) mockBaseCtx.proxy();
126        }
127    
128        /**
129         * Creates a mock for a Context with the specified prefix.
130         *
131         * @param prefix the prefix
132         * @return the mock for the context
133         */
134        private Mock createCtxMock(String prefix)
135        {
136            Mock mockCtx = new Mock(Context.class);
137            for (int i = 0; i < PROP_NAMES.length; i++)
138            {
139                bind(mockCtx, prefix + PROP_NAMES[i], PROP_VALUES[i]);
140                String errProp = (prefix.length() > 0) ? PROP_NAMES[i] : PREFIX
141                        + PROP_NAMES[i];
142                bindError(mockCtx, errProp);
143            }
144            for (int i = 0; i < MISSING_NAMES.length; i++)
145            {
146                bindError(mockCtx, MISSING_NAMES[i]);
147            }
148            mockCtx.matchAndReturn("hashCode", System.identityHashCode(mockCtx.proxy()));
149    
150            return mockCtx;
151        }
152    
153        /**
154         * Binds a property value to the mock context.
155         *
156         * @param mockCtx the context
157         * @param name the name of the property
158         * @param value the value of the property
159         */
160        private void bind(Mock mockCtx, String name, String value)
161        {
162            mockCtx.matchAndReturn(METHOD_LOOKUP, C.eq(name), value);
163            bindError(mockCtx, name + MISSING_PROP);
164        }
165    
166        /**
167         * Configures the mock to expect a call for a non existing property.
168         *
169         * @param mockCtx the mock
170         * @param name the name of the property
171         */
172        private void bindError(Mock mockCtx, String name)
173        {
174            mockCtx.matchAndThrow(METHOD_LOOKUP, C.eq(name),
175                    new NameNotFoundException("unknown property"));
176        }
177    
178        /**
179         * Creates and initializes a mock for a naming enumeration.
180         *
181         * @param mockCtx the mock representing the context
182         * @param names the names contained in the iteration
183         * @param values the corresponding values
184         * @param close a flag whether the enumeration should expect to be closed
185         * @return the mock for the enumeration
186         */
187        private Mock createEnumMock(Mock mockCtx, String[] names, Object[] values,
188                boolean close)
189        {
190            Mock mockEnum = new Mock(NamingEnumeration.class);
191            for (int i = 0; i < names.length; i++)
192            {
193                addEnumPair(mockEnum, names[i], values[i]);
194            }
195            if (close)
196            {
197                closeEnum(mockEnum);
198            }
199            return mockEnum;
200        }
201    
202        /**
203         * Creates and initializes a mock for a naming enumeration that expects to
204         * be closed. This is a shortcut of createEnumMock(mockCtx, names, values,
205         * true);
206         *
207         * @param mockCtx the mock representing the context
208         * @param names the names contained in the iteration
209         * @param values the corresponding values
210         * @return the mock for the enumeration
211         */
212        private Mock createEnumMock(Mock mockCtx, String[] names, Object[] values)
213        {
214            return createEnumMock(mockCtx, names, values, true);
215        }
216    
217        /**
218         * Adds a new name-and-value pair to an enum mock.
219         *
220         * @param mockEnum the enum mock
221         * @param name the name
222         * @param value the value
223         */
224        private void addEnumPair(Mock mockEnum, String name, Object value)
225        {
226            NameClassPair ncp = new NameClassPair(name, value.getClass().getName());
227            mockEnum.expectAndReturn("hasMore", true);
228            mockEnum.expectAndReturn("next", ncp);
229        }
230    
231        /**
232         * Closes an enumeration mock.
233         *
234         * @param mockEnum the mock
235         */
236        private void closeEnum(Mock mockEnum)
237        {
238            mockEnum.expectAndReturn("hasMore", false);
239            mockEnum.expect(METHOD_CLOSE);
240        }
241    }