1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration;
18  
19  import java.util.Hashtable;
20  
21  import javax.naming.Context;
22  import javax.naming.NameClassPair;
23  import javax.naming.NameNotFoundException;
24  import javax.naming.NamingEnumeration;
25  import javax.naming.NamingException;
26  import javax.naming.spi.InitialContextFactory;
27  
28  import com.mockobjects.dynamic.C;
29  import com.mockobjects.dynamic.Mock;
30  
31  /***
32   * A mock implementation of the <code>InitialContextFactory</code> interface.
33   * This implementation will return a mock context that contains some test data.
34   *
35   * @author <a
36   * href="http://commons.apache.org/configuration/team-list.html">Commons
37   * Configuration team</a>
38   * @version $Id: MockInitialContextFactory.java 561230 2007-07-31 04:17:09Z rahul $
39   */
40  public class MockInitialContextFactory implements InitialContextFactory
41  {
42      /***
43       * Constant for the use cycles environment property. If this property is
44       * present in the environment, a cyclic context will be created.
45       */
46      public static final String PROP_CYCLES = "useCycles";
47  
48      /*** Constant for the lookup method. */
49      private static final String METHOD_LOOKUP = "lookup";
50  
51      /*** Constant for the list method. */
52      private static final String METHOD_LIST = "list";
53  
54      /*** Constant for the close method.*/
55      private static final String METHOD_CLOSE = "close";
56  
57      /*** Constant for the name of the missing property. */
58      private static final String MISSING_PROP = "/missing";
59  
60      /*** Constant for the name of the prefix. */
61      private static final String PREFIX = "test/";
62  
63      /*** An array with the names of the supported properties. */
64      private static final String[] PROP_NAMES =
65      { "key", "key2", "short", "boolean", "byte", "double", "float", "integer",
66              "long", "onlyinjndi" };
67  
68      /*** An array with the values of the supported properties. */
69      private static final String[] PROP_VALUES =
70      { "jndivalue", "jndivalue2", "1", "true", "10", "10.25", "20.25", "10",
71              "1000000", "true" };
72  
73      /*** An array with properties that are requested, but are not in the context. */
74      private static final String[] MISSING_NAMES =
75      { "missing/list", "test/imaginarykey", "foo/bar" };
76  
77      /***
78       * Creates a <code>Context</code> object that is backed by a mock object.
79       * The mock context can be queried for the values of certain test
80       * properties. It also supports listing the contained (sub) properties.
81       *
82       * @param env the environment
83       * @return the context mock
84       */
85      public Context getInitialContext(Hashtable env) throws NamingException
86      {
87          boolean useCycles = env.containsKey(PROP_CYCLES);
88  
89          Mock mockTopCtx = createCtxMock(PREFIX);
90          Mock mockCycleCtx = createCtxMock("");
91          Mock mockPrfxCtx = createCtxMock("");
92          Mock mockBaseCtx = new Mock(Context.class);
93          mockBaseCtx.matchAndReturn(METHOD_LOOKUP, C.eq(""), mockTopCtx.proxy());
94          mockBaseCtx.matchAndReturn(METHOD_LOOKUP, C.eq("test"), mockPrfxCtx
95                  .proxy());
96          mockTopCtx.matchAndReturn(METHOD_LOOKUP, C.eq("test"), mockPrfxCtx
97                  .proxy());
98          mockPrfxCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
99                  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 }