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