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 */
017package org.apache.camel.util.jndi;
018
019import java.io.Serializable;
020import java.util.HashMap;
021import java.util.Hashtable;
022import java.util.Iterator;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import javax.naming.Binding;
027import javax.naming.CompositeName;
028import javax.naming.Context;
029import javax.naming.LinkRef;
030import javax.naming.Name;
031import javax.naming.NameClassPair;
032import javax.naming.NameNotFoundException;
033import javax.naming.NameParser;
034import javax.naming.NamingEnumeration;
035import javax.naming.NamingException;
036import javax.naming.NotContextException;
037import javax.naming.OperationNotSupportedException;
038import javax.naming.Reference;
039import javax.naming.spi.NamingManager;
040
041import org.apache.camel.spi.Injector;
042import org.apache.camel.util.CastUtils;
043import org.apache.camel.util.IntrospectionSupport;
044import org.apache.camel.util.ObjectHelper;
045import org.apache.camel.util.ReflectionInjector;
046
047/**
048 * A default JNDI context
049 *
050 * @version 
051 */
052public 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<>() : new Hashtable<>(environment);
078        this.bindings = bindings;
079        treeBindings = new HashMap<>();
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<>(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<>(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<>();
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}