001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.mock.jndi;
018
019import java.util.HashMap;
020import java.util.Hashtable;
021import java.util.Iterator;
022import java.util.Map;
023import javax.naming.Binding;
024import javax.naming.Context;
025import javax.naming.Name;
026import javax.naming.NameClassPair;
027import javax.naming.NameNotFoundException;
028import javax.naming.NameParser;
029import javax.naming.NamingEnumeration;
030import javax.naming.NamingException;
031import javax.naming.OperationNotSupportedException;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036import org.springframework.util.StringUtils;
037
038/**
039 * Simple implementation of a JNDI naming context.
040 * Only supports binding plain Objects to String names.
041 * Mainly for test environments, but also usable for standalone applications.
042 *
043 * <p>This class is not intended for direct usage by applications, although it
044 * can be used for example to override JndiTemplate's {@code createInitialContext}
045 * method in unit tests. Typically, SimpleNamingContextBuilder will be used to
046 * set up a JVM-level JNDI environment.
047 *
048 * @author Rod Johnson
049 * @author Juergen Hoeller
050 * @see SimpleNamingContextBuilder
051 * @see org.springframework.jndi.JndiTemplate#createInitialContext
052 */
053public class SimpleNamingContext implements Context {
054
055        private final Log logger = LogFactory.getLog(getClass());
056
057        private final String root;
058
059        private final Hashtable<String, Object> boundObjects;
060
061        private final Hashtable<String, Object> environment = new Hashtable<String, Object>();
062
063
064        /**
065         * Create a new naming context.
066         */
067        public SimpleNamingContext() {
068                this("");
069        }
070
071        /**
072         * Create a new naming context with the given naming root.
073         */
074        public SimpleNamingContext(String root) {
075                this.root = root;
076                this.boundObjects = new Hashtable<String, Object>();
077        }
078
079        /**
080         * Create a new naming context with the given naming root,
081         * the given name/object map, and the JNDI environment entries.
082         */
083        public SimpleNamingContext(String root, Hashtable<String, Object> boundObjects, Hashtable<String, Object> env) {
084                this.root = root;
085                this.boundObjects = boundObjects;
086                if (env != null) {
087                        this.environment.putAll(env);
088                }
089        }
090
091
092        // Actual implementations of Context methods follow
093
094        @Override
095        public NamingEnumeration<NameClassPair> list(String root) throws NamingException {
096                if (logger.isDebugEnabled()) {
097                        logger.debug("Listing name/class pairs under [" + root + "]");
098                }
099                return new NameClassPairEnumeration(this, root);
100        }
101
102        @Override
103        public NamingEnumeration<Binding> listBindings(String root) throws NamingException {
104                if (logger.isDebugEnabled()) {
105                        logger.debug("Listing bindings under [" + root + "]");
106                }
107                return new BindingEnumeration(this, root);
108        }
109
110        /**
111         * Look up the object with the given name.
112         * <p>Note: Not intended for direct use by applications.
113         * Will be used by any standard InitialContext JNDI lookups.
114         * @throws javax.naming.NameNotFoundException if the object could not be found
115         */
116        @Override
117        public Object lookup(String lookupName) throws NameNotFoundException {
118                String name = this.root + lookupName;
119                if (logger.isDebugEnabled()) {
120                        logger.debug("Static JNDI lookup: [" + name + "]");
121                }
122                if (name.isEmpty()) {
123                        return new SimpleNamingContext(this.root, this.boundObjects, this.environment);
124                }
125                Object found = this.boundObjects.get(name);
126                if (found == null) {
127                        if (!name.endsWith("/")) {
128                                name = name + "/";
129                        }
130                        for (String boundName : this.boundObjects.keySet()) {
131                                if (boundName.startsWith(name)) {
132                                        return new SimpleNamingContext(name, this.boundObjects, this.environment);
133                                }
134                        }
135                        throw new NameNotFoundException(
136                                        "Name [" + this.root + lookupName + "] not bound; " + this.boundObjects.size() + " bindings: [" +
137                                        StringUtils.collectionToDelimitedString(this.boundObjects.keySet(), ",") + "]");
138                }
139                return found;
140        }
141
142        @Override
143        public Object lookupLink(String name) throws NameNotFoundException {
144                return lookup(name);
145        }
146
147        /**
148         * Bind the given object to the given name.
149         * Note: Not intended for direct use by applications
150         * if setting up a JVM-level JNDI environment.
151         * Use SimpleNamingContextBuilder to set up JNDI bindings then.
152         * @see org.springframework.mock.jndi.SimpleNamingContextBuilder#bind
153         */
154        @Override
155        public void bind(String name, Object obj) {
156                if (logger.isInfoEnabled()) {
157                        logger.info("Static JNDI binding: [" + this.root + name + "] = [" + obj + "]");
158                }
159                this.boundObjects.put(this.root + name, obj);
160        }
161
162        @Override
163        public void unbind(String name) {
164                if (logger.isInfoEnabled()) {
165                        logger.info("Static JNDI remove: [" + this.root + name + "]");
166                }
167                this.boundObjects.remove(this.root + name);
168        }
169
170        @Override
171        public void rebind(String name, Object obj) {
172                bind(name, obj);
173        }
174
175        @Override
176        public void rename(String oldName, String newName) throws NameNotFoundException {
177                Object obj = lookup(oldName);
178                unbind(oldName);
179                bind(newName, obj);
180        }
181
182        @Override
183        public Context createSubcontext(String name) {
184                String subcontextName = this.root + name;
185                if (!subcontextName.endsWith("/")) {
186                        subcontextName += "/";
187                }
188                Context subcontext = new SimpleNamingContext(subcontextName, this.boundObjects, this.environment);
189                bind(name, subcontext);
190                return subcontext;
191        }
192
193        @Override
194        public void destroySubcontext(String name) {
195                unbind(name);
196        }
197
198        @Override
199        public String composeName(String name, String prefix) {
200                return prefix + name;
201        }
202
203        @Override
204        public Hashtable<String, Object> getEnvironment() {
205                return this.environment;
206        }
207
208        @Override
209        public Object addToEnvironment(String propName, Object propVal) {
210                return this.environment.put(propName, propVal);
211        }
212
213        @Override
214        public Object removeFromEnvironment(String propName) {
215                return this.environment.remove(propName);
216        }
217
218        @Override
219        public void close() {
220        }
221
222
223        // Unsupported methods follow: no support for javax.naming.Name
224
225        @Override
226        public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
227                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
228        }
229
230        @Override
231        public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
232                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
233        }
234
235        @Override
236        public Object lookup(Name name) throws NamingException {
237                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
238        }
239
240        @Override
241        public Object lookupLink(Name name) throws NamingException {
242                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
243        }
244
245        @Override
246        public void bind(Name name, Object obj) throws NamingException {
247                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
248        }
249
250        @Override
251        public void unbind(Name name) throws NamingException {
252                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
253        }
254
255        @Override
256        public void rebind(Name name, Object obj) throws NamingException {
257                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
258        }
259
260        @Override
261        public void rename(Name oldName, Name newName) throws NamingException {
262                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
263        }
264
265        @Override
266        public Context createSubcontext(Name name) throws NamingException {
267                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
268        }
269
270        @Override
271        public void destroySubcontext(Name name) throws NamingException {
272                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
273        }
274
275        @Override
276        public String getNameInNamespace() throws NamingException {
277                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
278        }
279
280        @Override
281        public NameParser getNameParser(Name name) throws NamingException {
282                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
283        }
284
285        @Override
286        public NameParser getNameParser(String name) throws NamingException {
287                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
288        }
289
290        @Override
291        public Name composeName(Name name, Name prefix) throws NamingException {
292                throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
293        }
294
295
296        private abstract static class AbstractNamingEnumeration<T> implements NamingEnumeration<T> {
297
298                private final Iterator<T> iterator;
299
300                private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException {
301                        if (!proot.isEmpty() && !proot.endsWith("/")) {
302                                proot = proot + "/";
303                        }
304                        String root = context.root + proot;
305                        Map<String, T> contents = new HashMap<String, T>();
306                        for (String boundName : context.boundObjects.keySet()) {
307                                if (boundName.startsWith(root)) {
308                                        int startIndex = root.length();
309                                        int endIndex = boundName.indexOf('/', startIndex);
310                                        String strippedName =
311                                                        (endIndex != -1 ? boundName.substring(startIndex, endIndex) : boundName.substring(startIndex));
312                                        if (!contents.containsKey(strippedName)) {
313                                                try {
314                                                        contents.put(strippedName, createObject(strippedName, context.lookup(proot + strippedName)));
315                                                }
316                                                catch (NameNotFoundException ex) {
317                                                        // cannot happen
318                                                }
319                                        }
320                                }
321                        }
322                        if (contents.size() == 0) {
323                                throw new NamingException("Invalid root: [" + context.root + proot + "]");
324                        }
325                        this.iterator = contents.values().iterator();
326                }
327
328                protected abstract T createObject(String strippedName, Object obj);
329
330                @Override
331                public boolean hasMore() {
332                        return this.iterator.hasNext();
333                }
334
335                @Override
336                public T next() {
337                        return this.iterator.next();
338                }
339
340                @Override
341                public boolean hasMoreElements() {
342                        return this.iterator.hasNext();
343                }
344
345                @Override
346                public T nextElement() {
347                        return this.iterator.next();
348                }
349
350                @Override
351                public void close() {
352                }
353        }
354
355
356        private static final class NameClassPairEnumeration extends AbstractNamingEnumeration<NameClassPair> {
357
358                private NameClassPairEnumeration(SimpleNamingContext context, String root) throws NamingException {
359                        super(context, root);
360                }
361
362                @Override
363                protected NameClassPair createObject(String strippedName, Object obj) {
364                        return new NameClassPair(strippedName, obj.getClass().getName());
365                }
366        }
367
368
369        private static final class BindingEnumeration extends AbstractNamingEnumeration<Binding> {
370
371                private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException {
372                        super(context, root);
373                }
374
375                @Override
376                protected Binding createObject(String strippedName, Object obj) {
377                        return new Binding(strippedName, obj);
378                }
379        }
380
381}