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