View Javadoc
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.configuration2;
18  
19  import static org.mockito.Mockito.mock;
20  import static org.mockito.Mockito.when;
21  
22  import java.util.ArrayList;
23  import java.util.Hashtable;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import javax.naming.Context;
28  import javax.naming.NameClassPair;
29  import javax.naming.NameNotFoundException;
30  import javax.naming.NamingEnumeration;
31  import javax.naming.NamingException;
32  import javax.naming.spi.InitialContextFactory;
33  
34  /**
35   * A mock implementation of the {@code InitialContextFactory} interface. This implementation will return a mock context
36   * that contains some test data.
37   */
38  public class MockInitialContextFactory implements InitialContextFactory {
39  
40      /**
41       * A {@link NamingEnumeration} implementation that's backed by an iterator.
42       */
43      private static final class ListBasedNamingEnumeration implements NamingEnumeration<NameClassPair> {
44  
45          private final Iterator<NameClassPair> iterator;
46  
47          private ListBasedNamingEnumeration(final List<NameClassPair> pairs) {
48              this.iterator = pairs.iterator();
49          }
50  
51          @Override
52          public boolean hasMore() throws NamingException {
53              return hasMoreElements();
54          }
55  
56          @Override
57          public boolean hasMoreElements() {
58              return iterator.hasNext();
59          }
60  
61          @Override
62          public NameClassPair next() throws NamingException {
63              return nextElement();
64          }
65  
66          @Override
67          public NameClassPair nextElement() {
68              return iterator.next();
69          }
70  
71          @Override
72          public void close() throws NamingException {
73          }
74      }
75  
76      /**
77       * Constant for the use cycles environment property. If this property is present in the environment, a cyclic context
78       * will be created.
79       */
80      public static final String PROP_CYCLES = "useCycles";
81  
82      /** Constant for the name of the missing property. */
83      private static final String MISSING_PROP = "/missing";
84  
85      /** Constant for the name of the prefix. */
86      private static final String PREFIX = "test/";
87  
88      /** An array with the names of the supported properties. */
89      private static final String[] PROP_NAMES = {"key", "key2", "short", "boolean", "byte", "double", "float", "integer", "long", "onlyinjndi"};
90  
91      /** An array with the values of the supported properties. */
92      private static final String[] PROP_VALUES = {"jndivalue", "jndivalue2", "1", "true", "10", "10.25", "20.25", "10", "1000000", "true"};
93  
94      /** An array with properties that are requested, but are not in the context. */
95      private static final String[] MISSING_NAMES = {"missing/list", "test/imaginarykey", "foo/bar"};
96  
97      /**
98       * Adds a new name-and-value pair to list of {@link NameClassPair}s.
99       *
100      * @param pairs the list to add to
101      * @param name the name
102      * @param value the value
103      */
104     private void addEnumPair(final List<NameClassPair> pairs, final String name, final Object value) {
105         final NameClassPair ncp = new NameClassPair(name, value.getClass().getName());
106         pairs.add(ncp);
107     }
108 
109     /**
110      * Binds a property value to the mock context.
111      *
112      * @param mockCtx the context
113      * @param name the name of the property
114      * @param value the value of the property
115      */
116     private void bind(final Context mockCtx, final String name, final String value) throws NamingException {
117         when(mockCtx.lookup(name)).thenReturn(value);
118         bindError(mockCtx, name + MISSING_PROP);
119     }
120 
121     /**
122      * Configures the mock to expect a call for a non existing property.
123      *
124      * @param mockCtx the mock
125      * @param name the name of the property
126      */
127     private void bindError(final Context mockCtx, final String name) throws NamingException {
128         when(mockCtx.lookup(name)).thenThrow(new NameNotFoundException("unknown property"));
129     }
130 
131     /**
132      * Creates a mock for a Context with the specified prefix.
133      *
134      * @param prefix the prefix
135      * @return the mock for the context
136      */
137     private Context createCtxMock(final String prefix) throws NamingException {
138         final Context mockCtx = mock(Context.class);
139         for (int i = 0; i < PROP_NAMES.length; i++) {
140             bind(mockCtx, prefix + PROP_NAMES[i], PROP_VALUES[i]);
141             final String errProp = prefix.isEmpty() ? PREFIX + PROP_NAMES[i] : PROP_NAMES[i];
142             bindError(mockCtx, errProp);
143         }
144         for (final String element : MISSING_NAMES) {
145             bindError(mockCtx, element);
146         }
147 
148         return mockCtx;
149     }
150 
151     /**
152      * Creates and initializes a naming enumeration. This is a shortcut of wrapping the result of
153      * {@link #createNameClassPairs(String[], Object[])} in an instance of {@link ListBasedNamingEnumeration}.
154      *
155      * @param names the names contained in the iteration
156      * @param values the corresponding values
157      * @return the mock for the enumeration
158      */
159     private NamingEnumeration<NameClassPair> createNamingEnumeration(final String[] names, final Object[] values) {
160         return new ListBasedNamingEnumeration(createNameClassPairs(names, values));
161     }
162 
163     /**
164      * Creates and initializes a list of {@link NameClassPair}s that can be used to create a naming enumeration.
165      *
166      * @param names the names contained in the iteration
167      * @param values the corresponding values
168      * @return the mock for the enumeration
169      */
170     private List<NameClassPair> createNameClassPairs(final String[] names, final Object[] values) {
171         final List<NameClassPair> pairs = new ArrayList<>();
172         for (int i = 0; i < names.length; i++) {
173             addEnumPair(pairs, names[i], values[i]);
174         }
175         return pairs;
176     }
177 
178     /**
179      * Creates a {@code Context} object that is backed by a mock object. The mock context can be queried for the values of
180      * certain test properties. It also supports listing the contained (sub) properties.
181      *
182      * @param env the environment
183      * @return the context mock
184      */
185     @Override
186     public Context getInitialContext(@SuppressWarnings("rawtypes") final Hashtable env) throws NamingException {
187         final boolean useCycles = env.containsKey(PROP_CYCLES);
188 
189         final Context mockTopCtx = createCtxMock(PREFIX);
190         final Context mockCycleCtx = createCtxMock("");
191         final Context mockPrfxCtx = createCtxMock("");
192         final Context mockBaseCtx = mock(Context.class);
193 
194         when(mockBaseCtx.lookup("")).thenReturn(mockTopCtx);
195         when(mockBaseCtx.lookup("test")).thenReturn(mockPrfxCtx);
196         when(mockTopCtx.lookup("test")).thenReturn(mockPrfxCtx);
197         when(mockPrfxCtx.list("")).thenAnswer(invocation -> createNamingEnumeration(PROP_NAMES, PROP_VALUES));
198 
199         if (useCycles) {
200             when(mockTopCtx.lookup("cycle")).thenReturn(mockCycleCtx);
201             when(mockTopCtx.list("")).thenAnswer(invocation ->
202                     createNamingEnumeration(new String[] {"test", "cycle"}, new Object[] {mockPrfxCtx, mockCycleCtx}));
203             when(mockCycleCtx.list("")).thenAnswer(invocation -> {
204                 final List<NameClassPair> pairs = createNameClassPairs(PROP_NAMES, PROP_VALUES);
205                 addEnumPair(pairs, "cycleCtx", mockCycleCtx);
206                 return new ListBasedNamingEnumeration(pairs);
207             });
208             when(mockCycleCtx.lookup("cycleCtx")).thenReturn(mockCycleCtx);
209         } else {
210             when(mockTopCtx.list("")).thenAnswer(invocation -> createNamingEnumeration(new String[] {"test"}, new Object[] {mockPrfxCtx}));
211         }
212         return mockBaseCtx;
213     }
214 }