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.geronimo.gjndi;
018    
019    import java.util.Collections;
020    import java.util.HashMap;
021    import java.util.HashSet;
022    import java.util.LinkedHashMap;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import javax.naming.Name;
027    import javax.naming.NamingException;
028    import javax.naming.ContextNotEmptyException;
029    
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.apache.geronimo.gbean.AbstractName;
033    import org.apache.geronimo.gbean.AbstractNameQuery;
034    import org.apache.geronimo.gbean.GBeanInfo;
035    import org.apache.geronimo.gbean.GBeanInfoBuilder;
036    import org.apache.geronimo.gbean.GBeanLifecycle;
037    import org.apache.geronimo.kernel.GBeanNotFoundException;
038    import org.apache.geronimo.kernel.Kernel;
039    import org.apache.geronimo.kernel.lifecycle.LifecycleAdapter;
040    import org.apache.geronimo.kernel.lifecycle.LifecycleListener;
041    import org.apache.xbean.naming.context.ContextAccess;
042    import org.apache.xbean.naming.context.WritableContext;
043    
044    /**
045     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
046     */
047    public class KernelContextGBean extends WritableContext implements GBeanLifecycle {
048        private static final Log log = LogFactory.getLog(KernelContextGBean.class);
049    
050        private final Kernel kernel;
051        private final AbstractNameQuery abstractNameQuery;
052        private final LifecycleListener listener = new ContextLifecycleListener();
053        private final Map<AbstractName, Set<Name>> bindingsByAbstractName = new HashMap<AbstractName, Set<Name>>();
054    
055        public KernelContextGBean(String nameInNamespace, AbstractNameQuery abstractNameQuery, Kernel kernel) throws NamingException {
056            super(nameInNamespace, Collections.EMPTY_MAP, ContextAccess.MODIFIABLE, false);
057            this.abstractNameQuery = abstractNameQuery;
058            this.kernel = kernel;
059        }
060    
061        public synchronized void doStart() {
062            kernel.getLifecycleMonitor().addLifecycleListener(listener, abstractNameQuery);
063            Set<AbstractName> set = kernel.listGBeans(abstractNameQuery);
064            for (AbstractName abstractName : set) {
065                try {
066                    if (kernel.isRunning(abstractName)) {
067                        addBinding(abstractName);
068                    }
069                } catch (NamingException e) {
070                    log.error("Error adding binding for " + abstractName, e);
071                }
072            }
073    
074        }
075    
076        public void doStop() {
077            destroy();
078        }
079    
080        public void doFail() {
081            destroy();
082        }
083    
084        private synchronized void destroy() {
085            kernel.getLifecycleMonitor().removeLifecycleListener(listener);
086            Set<AbstractName> abstractNames = new HashSet<AbstractName>(bindingsByAbstractName.keySet());
087            for (AbstractName abstractName : abstractNames) {
088                removeBinding(abstractName);
089            }
090            bindingsByAbstractName.clear();
091        }
092    
093        private class ContextLifecycleListener extends LifecycleAdapter {
094            public void running(AbstractName abstractName) {
095                try {
096                    addBinding(abstractName);
097                } catch (NamingException e) {
098                    log.error("Error adding binding for " + abstractName);
099                }
100            }
101    
102            public void stopping(AbstractName abstractName) {
103                removeBinding(abstractName);
104            }
105    
106            public void stopped(AbstractName abstractName) {
107                removeBinding(abstractName);
108            }
109    
110            public void failed(AbstractName abstractName) {
111                removeBinding(abstractName);
112            }
113    
114            public void unloaded(AbstractName abstractName) {
115                removeBinding(abstractName);
116            }
117        }
118    
119        /**
120         * Binds the specified gbean.  This method uses createBindingName and preprocessValue before binding the object.
121         *
122         * @param abstractName the abstract name of the gbean to bind
123         * @throws NamingException if an error occurs during binding
124         */
125        protected synchronized void addBinding(AbstractName abstractName) throws NamingException {
126            if (bindingsByAbstractName.containsKey(abstractName)) {
127                // previously bound
128                return;
129            }
130    
131            // get the gbean
132            Object instance;
133            try {
134                instance = kernel.getGBean(abstractName);
135            } catch (GBeanNotFoundException e) {
136                throw (NamingException)new NamingException("GBean not found: " + abstractName).initCause(e);
137            }
138    
139            // create the bindings for this object
140            Map<Name, Object> bindings = createBindings(abstractName, instance);
141            if (bindings == null || bindings.isEmpty()) {
142                return;
143            }
144    
145            // bind the value
146            for (Map.Entry<Name, Object> entry : bindings.entrySet()) {
147                Name name = entry.getKey();
148                Object value = entry.getValue();
149                addBinding(abstractName, name, value);
150            }
151    
152            // remember where we bound this value
153            bindingsByAbstractName.put(abstractName, bindings.keySet());
154        }
155    
156        private Map<Name, LinkedHashMap<AbstractName, Object>> bindingsByName = new HashMap<Name, LinkedHashMap<AbstractName, Object>>();
157    
158        private synchronized void addBinding(AbstractName abstractName, Name name, Object value) throws NamingException {
159            LinkedHashMap<AbstractName, Object> bindings = bindingsByName.get(name);
160            if (bindings == null) {
161                addDeepBinding(name, value, true, true);
162    
163                bindings = new LinkedHashMap<AbstractName, Object>();
164                bindings.put(abstractName, value);
165                bindingsByName.put(name, bindings);
166                log.info("bound gbean " + abstractName + " at name " + name);
167            } else {
168                bindings.put(abstractName, value);
169            }
170        }
171    
172        /**
173         * Unbinds the specified gbean.
174         *
175         * @param abstractName the abstract name of the gbean to unbind
176         */
177        protected synchronized void removeBinding(AbstractName abstractName) {
178            Set<Name> bindingNames = bindingsByAbstractName.remove(abstractName);
179            if (bindingNames == null) return;
180    
181            for (Name name : bindingNames) {
182    
183                LinkedHashMap<AbstractName, Object> bindings = bindingsByName.get(name);
184                if (bindings == null) continue;
185    
186                if (first(bindings).getKey().equals(abstractName)) {
187                    bindings.remove(abstractName);
188                    Map.Entry<AbstractName, Object> newEntry = first(bindings);
189                    if (newEntry != null) {
190                        Object newValue = newEntry.getValue();
191                        try {
192                            addDeepBinding(name, newValue, true, true);
193                        } catch (NamingException e) {
194                            boolean logged = false;
195                            try {
196                                removeDeepBinding(name, true);
197                            } catch (NamingException e1) {
198                                logged = true;
199                                log.error("Unable to remove binding " + name + " to " + abstractName, e);
200                            }
201                            if (!logged) log.error("Unable to rebind binding " + name + " to " + newEntry.getKey());
202                        }
203                    } else {
204                        bindingsByName.remove(name);
205                        try {
206                            removeDeepBinding(name, true, true);
207                        } catch (ContextNotEmptyException e) {
208                            //ignore
209                        } catch (NamingException e) {
210                            log.error("Unable to remove binding " + name + " to " + abstractName, e);
211                        }
212                        log.info("unbound gbean " + abstractName + " at name " + name);
213                    }
214                } else {
215                    bindings.remove(abstractName);
216                }
217            }
218        }
219    
220        private static Map.Entry<AbstractName, Object> first(LinkedHashMap<AbstractName, Object> map) {
221            if (map.isEmpty()) return null;
222            return map.entrySet().iterator().next();
223        }
224    
225        protected Map<Name, Object> createBindings(AbstractName abstractName, Object value) throws NamingException {
226            // generate a name for this binding
227            Name name = createBindingName(abstractName, value);
228            if (name == null) return null;
229    
230            // give sub classes a chance to preprocess the value
231            value = preprocessVaue(abstractName, name, value);
232            if (value == null) return null;
233    
234            return Collections.singletonMap(name, value);
235        }
236    
237        /**
238         * Create a name under which we will bind the specified gbean with the specified value.
239         * By default, this method simply returns the "name" element of the abstract name
240         *
241         * @param abstractName the abstract name of the gbean to bind
242         * @param value        the gbean instance
243         * @return the name under which the gbean should be bound
244         * @throws javax.naming.NamingException should something go wrong
245         */
246        protected Name createBindingName(AbstractName abstractName, Object value) throws NamingException {
247            String shortName = (String) abstractName.getName().get("name");
248            return getNameParser().parse(shortName);
249        }
250    
251        /**
252         * Preprocess the value before it is bound.  This is usefult for wrapping values with reference objects.
253         * By default, this method simply return the value.
254         *
255         * @param abstractName the abstract name of the gbean to bind
256         * @param name         the name under which the gbean will be bound
257         * @param value        the gbean instance
258         * @return the value to bind
259         * @throws javax.naming.NamingException should something go wrong
260         */
261        protected Object preprocessVaue(AbstractName abstractName, Name name, Object value) throws NamingException {
262            return value;
263        }
264    
265        public static final GBeanInfo GBEAN_INFO;
266    
267        public static GBeanInfo getGBeanInfo() {
268            return GBEAN_INFO;
269        }
270    
271        static {
272            GBeanInfoBuilder builder = GBeanInfoBuilder.createStatic(KernelContextGBean.class, "Context");
273            builder.addAttribute("nameInNamespace", String.class, true);
274            builder.addAttribute("abstractNameQuery", AbstractNameQuery.class, true);
275            builder.setConstructor(new String[]{"nameInNamespace", "abstractNameQuery", "kernel"});
276            GBEAN_INFO = builder.getBeanInfo();
277        }
278    }