001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.geronimo.gjndi.binding;
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import org.apache.geronimo.gbean.AbstractName;
023    import org.apache.geronimo.gbean.AbstractNameQuery;
024    import org.apache.geronimo.gbean.GBeanInfo;
025    import org.apache.geronimo.gbean.GBeanInfoBuilder;
026    import org.apache.geronimo.gbean.GBeanLifecycle;
027    import org.apache.geronimo.kernel.GBeanNotFoundException;
028    import org.apache.geronimo.kernel.Kernel;
029    import org.apache.geronimo.kernel.lifecycle.LifecycleAdapter;
030    import org.apache.geronimo.kernel.lifecycle.LifecycleListener;
031    
032    import javax.naming.Context;
033    import javax.naming.NamingException;
034    import java.util.HashSet;
035    import java.util.Iterator;
036    import java.util.LinkedHashMap;
037    import java.util.Map;
038    import java.util.Set;
039    
040    /**
041     * @version $Rev$ $Date$
042     */
043    public class GBeanBinding implements GBeanLifecycle {
044        private static final Log log = LogFactory.getLog(GBeanBinding.class);
045    
046        private final Context context;
047        private final String name;
048        private final AbstractNameQuery abstractNameQuery;
049        private final Kernel kernel;
050    
051        private final LifecycleListener listener = new GBeanLifecycleListener();
052        private final LinkedHashMap bindings = new LinkedHashMap();
053    
054        public GBeanBinding(Context context, String name, AbstractNameQuery abstractNameQuery, Kernel kernel) {
055            this.context = context;
056            this.name = name;
057            this.abstractNameQuery = abstractNameQuery;
058            this.kernel = kernel;
059        }
060    
061        public synchronized void doStart() {
062            kernel.getLifecycleMonitor().addLifecycleListener(listener, abstractNameQuery);
063            Set set = kernel.listGBeans(abstractNameQuery);
064            for (Iterator iterator = set.iterator(); iterator.hasNext();) {
065                AbstractName abstractName = (AbstractName) iterator.next();
066                try {
067                    if (kernel.isRunning(abstractName)) {
068                        addBinding(abstractName);
069                    }
070                } catch (NamingException e) {
071                    log.error("Error adding binding for " + abstractName, e);
072                }
073            }
074    
075        }
076    
077        public void doStop() {
078            destroy();
079        }
080    
081        public void doFail() {
082            destroy();
083        }
084    
085        private synchronized void destroy() {
086            kernel.getLifecycleMonitor().removeLifecycleListener(listener);
087            Set abstractNames = new HashSet(bindings.keySet());
088            for (Iterator iterator = abstractNames.iterator(); iterator.hasNext();) {
089                AbstractName abstractName = (AbstractName) iterator.next();
090                removeBinding(abstractName);
091            }
092            bindings.clear();
093        }
094    
095        private class GBeanLifecycleListener extends LifecycleAdapter {
096            public void running(AbstractName abstractName) {
097                try {
098                    addBinding(abstractName);
099                } catch (NamingException e) {
100                    log.error("Error adding binding for " + abstractName);
101                }
102            }
103    
104            public void stopping(AbstractName abstractName) {
105                removeBinding(abstractName);
106            }
107    
108            public void stopped(AbstractName abstractName) {
109                removeBinding(abstractName);
110            }
111    
112            public void failed(AbstractName abstractName) {
113                removeBinding(abstractName);
114            }
115    
116            public void unloaded(AbstractName abstractName) {
117                removeBinding(abstractName);
118            }
119        }
120    
121        /**
122         * Binds the specified gbean.  This method uses createBindingName and preprocessValue before binding the object.
123         *
124         * @param abstractName the abstract name of the gbean to bind
125         * @throws NamingException if an error occurs during binding
126         */
127        protected synchronized void addBinding(AbstractName abstractName) throws NamingException {
128            if (bindings.containsKey(abstractName)) {
129                // previously bound
130                return;
131            }
132    
133            // get the gbean
134            Object instance = null;
135            try {
136                instance = kernel.getGBean(abstractName);
137            } catch (GBeanNotFoundException e) {
138                throw new NamingException("GBean not found: " + abstractName);
139            }
140    
141            // preprocess the instance
142            instance = preprocessVaue(abstractName, instance);
143    
144            addBinding(abstractName, instance);
145        }
146    
147        private synchronized void addBinding(AbstractName abstractName, Object value) throws NamingException {
148            if (bindings.isEmpty()) {
149                context.bind(name, value);
150            }
151            bindings.put(abstractName, value);
152        }
153    
154        /**
155         * Unbinds the specified gbean.
156         *
157         * @param abstractName the abstract name of the gbean to unbind
158         */
159        protected synchronized void removeBinding(AbstractName abstractName) {
160            if (first(bindings).getKey().equals(abstractName)) {
161                Object oldValue = bindings.remove(abstractName);
162                Map.Entry newEntry = first(bindings);
163                if (newEntry != null) {
164                    Object newAbstractName = newEntry.getValue();
165                    Object newValue = newEntry.getValue();
166                    try {
167                        context.rebind(name, newValue);
168                    } catch (NamingException e) {
169                        boolean unbound = unbind(abstractName, oldValue);
170                        // avoid double logging
171                        if (unbound) log.error("Unable to rebind binding " + name + " to " + newAbstractName);
172                    }
173                } else {
174                    unbind(abstractName, oldValue);
175                }
176            } else {
177                bindings.remove(abstractName);
178            }
179        }
180    
181        private boolean unbind(AbstractName abstractName, Object value) {
182            // first check if we are still bound
183            try {
184                if (context.lookup(name) != value) {
185                    return true;
186                }
187            } catch (NamingException ignored) {
188                // binding doesn't exist
189                return true;
190            }
191    
192            try {
193                context.unbind(name);
194                return true;
195            } catch (NamingException e1) {
196                log.error("Unable to remove binding " + name + " to " + abstractName, e1);
197            }
198            return false;
199        }
200    
201        private static Map.Entry first(LinkedHashMap map) {
202            if (map.isEmpty()) return null;
203            return (Map.Entry) map.entrySet().iterator().next();
204        }
205    
206        /**
207         * Preprocess the value before it is bound.  This is usefult for wrapping values with reference objects.
208         * By default, this method simply return the value.
209         *
210         * @param abstractName the abstract name of the gbean to bind
211         * @param value        the gbean instance
212         * @return the value to bind
213         */
214        protected Object preprocessVaue(AbstractName abstractName, Object value) throws NamingException {
215            return value;
216        }
217    
218        public static final GBeanInfo GBEAN_INFO;
219    
220        public static GBeanInfo getGBeanInfo() {
221            return GBEAN_INFO;
222        }
223    
224        static {
225            GBeanInfoBuilder builder = GBeanInfoBuilder.createStatic(GBeanBinding.class, "GBeanBinding");
226            builder.addReference("Context", Context.class);
227            builder.addAttribute("name", String.class, true);
228            builder.addAttribute("abstractNameQuery", AbstractNameQuery.class, true);
229            builder.setConstructor(new String[]{"Context", "name", "abstractNameQuery", "kernel"});
230            GBEAN_INFO = builder.getBeanInfo();
231        }
232    }