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.camel.util.jndi;
018    
019    import java.io.Serializable;
020    import java.util.HashMap;
021    import java.util.Hashtable;
022    import java.util.Iterator;
023    import java.util.Map;
024    
025    import javax.naming.Binding;
026    import javax.naming.CompositeName;
027    import javax.naming.Context;
028    import javax.naming.LinkRef;
029    import javax.naming.Name;
030    import javax.naming.NameClassPair;
031    import javax.naming.NameNotFoundException;
032    import javax.naming.NameParser;
033    import javax.naming.NamingEnumeration;
034    import javax.naming.NamingException;
035    import javax.naming.NotContextException;
036    import javax.naming.OperationNotSupportedException;
037    import javax.naming.Reference;
038    import javax.naming.spi.NamingManager;
039    
040    import org.apache.camel.spi.Injector;
041    import org.apache.camel.util.IntrospectionSupport;
042    import org.apache.camel.util.ObjectHelper;
043    import org.apache.camel.util.ReflectionInjector;
044    
045    /**
046     * A default JNDI context
047     *
048     * @version $Revision: 747062 $
049     */
050    public class JndiContext implements Context, Serializable {
051        public static final String SEPARATOR = "/";
052        protected static final NameParser NAME_PARSER = new NameParser() {
053            public Name parse(String name) throws NamingException {
054                return new CompositeName(name);
055            }
056        };
057        protected static final Injector INJETOR = new ReflectionInjector();
058        private static final long serialVersionUID = -5754338187296859149L;
059    
060        private final Hashtable environment; // environment for this context
061        private final Map bindings; // bindings at my level
062        private final Map treeBindings; // all bindings under me
063        private boolean frozen;
064        private String nameInNamespace = "";
065    
066        public JndiContext() throws Exception {
067            this(new Hashtable());
068        }
069    
070        public JndiContext(Hashtable env) throws Exception {
071            this(env, createBindingsMapFromEnvironment(env));
072        }
073    
074        @SuppressWarnings("unchecked")
075        public JndiContext(Hashtable environment, Map bindings) {
076            if (environment == null) {
077                this.environment = new Hashtable();
078            } else {
079                this.environment = new Hashtable(environment);
080            }
081            this.bindings = bindings;
082            treeBindings = new HashMap();
083        }
084    
085        public JndiContext(Hashtable environment, Map bindings, String nameInNamespace) {
086            this(environment, bindings);
087            this.nameInNamespace = nameInNamespace;
088        }
089    
090        @SuppressWarnings("unchecked")
091        protected JndiContext(JndiContext clone, Hashtable env) {
092            this.bindings = clone.bindings;
093            this.treeBindings = clone.treeBindings;
094            this.environment = new Hashtable(env);
095        }
096    
097        protected JndiContext(JndiContext clone, Hashtable env, String nameInNamespace) {
098            this(clone, env);
099            this.nameInNamespace = nameInNamespace;
100        }
101    
102        /**
103         * A helper method to create the JNDI bindings from the input environment
104         * properties using $foo.class to point to a class name with $foo.* being
105         * properties set on the injected bean
106         */
107        @SuppressWarnings("unchecked")
108        public static Map createBindingsMapFromEnvironment(Hashtable env) throws Exception {
109            Map answer = new HashMap(env);
110    
111            for (Object object : env.entrySet()) {
112                Map.Entry entry = (Map.Entry)object;
113                Object key = entry.getKey();
114                Object value = entry.getValue();
115    
116                if (key instanceof String && value instanceof String) {
117                    String keyText = (String)key;
118                    String valueText = (String)value;
119                    if (keyText.endsWith(".class")) {
120                        Class<?> type = ObjectHelper.loadClass(valueText);
121                        if (type != null) {
122                            String newEntry = keyText.substring(0, keyText.length() - ".class".length());
123                            Object bean = createBean(type, answer, newEntry + ".");
124                            if (bean != null) {
125                                answer.put(newEntry, bean);
126                            }
127                        }
128                    }
129                }
130            }
131    
132            return answer;
133        }
134    
135        public void freeze() {
136            frozen = true;
137        }
138    
139        boolean isFrozen() {
140            return frozen;
141        }
142    
143        /**
144         * internalBind is intended for use only during setup or possibly by
145         * suitably synchronized superclasses. It binds every possible lookup into a
146         * map in each context. To do this, each context strips off one name segment
147         * and if necessary creates a new context for it. Then it asks that context
148         * to bind the remaining name. It returns a map containing all the bindings
149         * from the next context, plus the context it just created (if it in fact
150         * created it). (the names are suitably extended by the segment originally
151         * lopped off).
152         */
153        @SuppressWarnings("unchecked")
154        protected Map internalBind(String name, Object value) throws NamingException {
155            assert name != null && name.length() > 0;
156            assert !frozen;
157    
158            Map newBindings = new HashMap();
159            int pos = name.indexOf('/');
160            if (pos == -1) {
161                if (treeBindings.put(name, value) != null) {
162                    throw new NamingException("Something already bound at " + name);
163                }
164                bindings.put(name, value);
165                newBindings.put(name, value);
166            } else {
167                String segment = name.substring(0, pos);
168                assert segment != null;
169                assert !segment.equals("");
170                Object o = treeBindings.get(segment);
171                if (o == null) {
172                    o = newContext();
173                    treeBindings.put(segment, o);
174                    bindings.put(segment, o);
175                    newBindings.put(segment, o);
176                } else if (!(o instanceof JndiContext)) {
177                    throw new NamingException("Something already bound where a subcontext should go");
178                }
179                JndiContext defaultContext = (JndiContext)o;
180                String remainder = name.substring(pos + 1);
181                Map subBindings = defaultContext.internalBind(remainder, value);
182                for (Iterator iterator = subBindings.entrySet().iterator(); iterator.hasNext();) {
183                    Map.Entry entry = (Map.Entry)iterator.next();
184                    String subName = segment + "/" + (String)entry.getKey();
185                    Object bound = entry.getValue();
186                    treeBindings.put(subName, bound);
187                    newBindings.put(subName, bound);
188                }
189            }
190            return newBindings;
191        }
192    
193        protected JndiContext newContext() {
194            try {
195                return new JndiContext();
196            } catch (Exception e) {
197                throw new IllegalArgumentException(e);
198            }
199        }
200    
201        @SuppressWarnings("unchecked")
202        public Object addToEnvironment(String propName, Object propVal) throws NamingException {
203            return environment.put(propName, propVal);
204        }
205    
206        public Hashtable getEnvironment() throws NamingException {
207            return (Hashtable)environment.clone();
208        }
209    
210        public Object removeFromEnvironment(String propName) throws NamingException {
211            return environment.remove(propName);
212        }
213    
214        public Object lookup(String name) throws NamingException {
215            if (name.length() == 0) {
216                return this;
217            }
218            Object result = treeBindings.get(name);
219            if (result == null) {
220                result = bindings.get(name);
221            }
222            if (result == null) {
223                int pos = name.indexOf(':');
224                if (pos > 0) {
225                    String scheme = name.substring(0, pos);
226                    Context ctx = NamingManager.getURLContext(scheme, environment);
227                    if (ctx == null) {
228                        throw new NamingException("scheme " + scheme + " not recognized");
229                    }
230                    return ctx.lookup(name);
231                } else {
232                    // Split out the first name of the path
233                    // and look for it in the bindings map.
234                    CompositeName path = new CompositeName(name);
235    
236                    if (path.size() == 0) {
237                        return this;
238                    } else {
239                        String first = path.get(0);
240                        Object value = bindings.get(first);
241                        if (value == null) {
242                            throw new NameNotFoundException(name);
243                        } else if (value instanceof Context && path.size() > 1) {
244                            Context subContext = (Context)value;
245                            value = subContext.lookup(path.getSuffix(1));
246                        }
247                        return value;
248                    }
249                }
250            }
251            if (result instanceof LinkRef) {
252                LinkRef ref = (LinkRef)result;
253                result = lookup(ref.getLinkName());
254            }
255            if (result instanceof Reference) {
256                try {
257                    result = NamingManager.getObjectInstance(result, null, null, this.environment);
258                } catch (NamingException e) {
259                    throw e;
260                } catch (Exception e) {
261                    throw (NamingException)new NamingException("could not look up : " + name).initCause(e);
262                }
263            }
264            if (result instanceof JndiContext) {
265                String prefix = getNameInNamespace();
266                if (prefix.length() > 0) {
267                    prefix = prefix + SEPARATOR;
268                }
269                result = new JndiContext((JndiContext)result, environment, prefix + name);
270            }
271            return result;
272        }
273    
274        public Object lookup(Name name) throws NamingException {
275            return lookup(name.toString());
276        }
277    
278        public Object lookupLink(String name) throws NamingException {
279            return lookup(name);
280        }
281    
282        public Name composeName(Name name, Name prefix) throws NamingException {
283            Name result = (Name)prefix.clone();
284            result.addAll(name);
285            return result;
286        }
287    
288        public String composeName(String name, String prefix) throws NamingException {
289            CompositeName result = new CompositeName(prefix);
290            result.addAll(new CompositeName(name));
291            return result.toString();
292        }
293    
294        public NamingEnumeration list(String name) throws NamingException {
295            Object o = lookup(name);
296            if (o == this) {
297                return new ListEnumeration();
298            } else if (o instanceof Context) {
299                return ((Context)o).list("");
300            } else {
301                throw new NotContextException();
302            }
303        }
304    
305        public NamingEnumeration listBindings(String name) throws NamingException {
306            Object o = lookup(name);
307            if (o == this) {
308                return new ListBindingEnumeration();
309            } else if (o instanceof Context) {
310                return ((Context)o).listBindings("");
311            } else {
312                throw new NotContextException();
313            }
314        }
315    
316        public Object lookupLink(Name name) throws NamingException {
317            return lookupLink(name.toString());
318        }
319    
320        public NamingEnumeration list(Name name) throws NamingException {
321            return list(name.toString());
322        }
323    
324        public NamingEnumeration listBindings(Name name) throws NamingException {
325            return listBindings(name.toString());
326        }
327    
328        public void bind(Name name, Object value) throws NamingException {
329            bind(name.toString(), value);
330        }
331    
332        public void bind(String name, Object value) throws NamingException {
333            if (isFrozen()) {
334                throw new OperationNotSupportedException();
335            } else {
336                internalBind(name, value);
337            }
338        }
339    
340        public void close() throws NamingException {
341            // ignore
342        }
343    
344        public Context createSubcontext(Name name) throws NamingException {
345            throw new OperationNotSupportedException();
346        }
347    
348        public Context createSubcontext(String name) throws NamingException {
349            throw new OperationNotSupportedException();
350        }
351    
352        public void destroySubcontext(Name name) throws NamingException {
353            throw new OperationNotSupportedException();
354        }
355    
356        public void destroySubcontext(String name) throws NamingException {
357            throw new OperationNotSupportedException();
358        }
359    
360        public String getNameInNamespace() throws NamingException {
361            return nameInNamespace;
362        }
363    
364        public NameParser getNameParser(Name name) throws NamingException {
365            return NAME_PARSER;
366        }
367    
368        public NameParser getNameParser(String name) throws NamingException {
369            return NAME_PARSER;
370        }
371    
372        public void rebind(Name name, Object value) throws NamingException {
373            bind(name, value);
374        }
375    
376        public void rebind(String name, Object value) throws NamingException {
377            bind(name, value);
378        }
379    
380        public void rename(Name oldName, Name newName) throws NamingException {
381            throw new OperationNotSupportedException();
382        }
383    
384        public void rename(String oldName, String newName) throws NamingException {
385            throw new OperationNotSupportedException();
386        }
387    
388        public void unbind(Name name) throws NamingException {
389            throw new OperationNotSupportedException();
390        }
391    
392        public void unbind(String name) throws NamingException {
393            bindings.remove(name);
394            treeBindings.remove(name);
395        }
396    
397        private abstract class LocalNamingEnumeration implements NamingEnumeration {
398            private Iterator i = bindings.entrySet().iterator();
399    
400            public boolean hasMore() throws NamingException {
401                return i.hasNext();
402            }
403    
404            public boolean hasMoreElements() {
405                return i.hasNext();
406            }
407    
408            protected Map.Entry getNext() {
409                return (Map.Entry)i.next();
410            }
411    
412            public void close() throws NamingException {
413            }
414        }
415    
416        private class ListEnumeration extends LocalNamingEnumeration {
417            ListEnumeration() {
418            }
419    
420            public Object next() throws NamingException {
421                return nextElement();
422            }
423    
424            public Object nextElement() {
425                Map.Entry entry = getNext();
426                return new NameClassPair((String)entry.getKey(), entry.getValue().getClass().getName());
427            }
428        }
429    
430        private class ListBindingEnumeration extends LocalNamingEnumeration {
431            ListBindingEnumeration() {
432            }
433    
434            public Object next() throws NamingException {
435                return nextElement();
436            }
437    
438            public Object nextElement() {
439                Map.Entry entry = getNext();
440                return new Binding((String)entry.getKey(), entry.getValue());
441            }
442        }
443    
444        protected static Object createBean(Class<?> type, Map properties, String prefix) throws Exception {
445            Object value = INJETOR.newInstance(type);
446            IntrospectionSupport.setProperties(value, properties, prefix);
447            return value;
448        }
449    }