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