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.binding;
018    
019    import org.apache.commons.logging.Log;
020    import org.apache.commons.logging.LogFactory;
021    import org.apache.geronimo.gbean.AbstractName;
022    import org.apache.geronimo.gbean.AbstractNameQuery;
023    import org.apache.geronimo.gbean.GBeanInfo;
024    import org.apache.geronimo.gbean.GBeanInfoBuilder;
025    import org.apache.geronimo.gbean.GBeanLifecycle;
026    import org.apache.geronimo.kernel.GBeanNotFoundException;
027    import org.apache.geronimo.kernel.Kernel;
028    import org.apache.geronimo.kernel.lifecycle.LifecycleAdapter;
029    import org.apache.geronimo.kernel.lifecycle.LifecycleListener;
030    
031    import javax.naming.Context;
032    import javax.naming.NamingException;
033    import java.util.HashSet;
034    import java.util.Iterator;
035    import java.util.LinkedHashMap;
036    import java.util.Map;
037    import java.util.Set;
038    
039    /**
040     * @version $Rev$ $Date$
041     */
042    public class GBeanBinding implements GBeanLifecycle {
043        private static final Log log = LogFactory.getLog(GBeanBinding.class);
044    
045        private final Context context;
046        private final String name;
047        private final AbstractNameQuery abstractNameQuery;
048        private final Kernel kernel;
049    
050        private final LifecycleListener listener = new GBeanLifecycleListener();
051        private final LinkedHashMap bindings = new LinkedHashMap();
052    
053        public GBeanBinding(Context context, String name, AbstractNameQuery abstractNameQuery, Kernel kernel) {
054            this.context = context;
055            this.name = name;
056            this.abstractNameQuery = abstractNameQuery;
057            this.kernel = kernel;
058        }
059    
060        public synchronized void doStart() {
061            kernel.getLifecycleMonitor().addLifecycleListener(listener, abstractNameQuery);
062            Set set = kernel.listGBeans(abstractNameQuery);
063            for (Iterator iterator = set.iterator(); iterator.hasNext();) {
064                AbstractName abstractName = (AbstractName) iterator.next();
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 abstractNames = new HashSet(bindings.keySet());
087            for (Iterator iterator = abstractNames.iterator(); iterator.hasNext();) {
088                AbstractName abstractName = (AbstractName) iterator.next();
089                removeBinding(abstractName);
090            }
091            bindings.clear();
092        }
093    
094        private class GBeanLifecycleListener extends LifecycleAdapter {
095            public void running(AbstractName abstractName) {
096                try {
097                    addBinding(abstractName);
098                } catch (NamingException e) {
099                    log.error("Error adding binding for " + abstractName);
100                }
101            }
102    
103            public void stopping(AbstractName abstractName) {
104                removeBinding(abstractName);
105            }
106    
107            public void stopped(AbstractName abstractName) {
108                removeBinding(abstractName);
109            }
110    
111            public void failed(AbstractName abstractName) {
112                removeBinding(abstractName);
113            }
114    
115            public void unloaded(AbstractName abstractName) {
116                removeBinding(abstractName);
117            }
118        }
119    
120        /**
121         * Binds the specified gbean.  This method uses createBindingName and preprocessValue before binding the object.
122         *
123         * @param abstractName the abstract name of the gbean to bind
124         * @throws NamingException if an error occurs during binding
125         */
126        protected synchronized void addBinding(AbstractName abstractName) throws NamingException {
127            if (bindings.containsKey(abstractName)) {
128                // previously bound
129                return;
130            }
131    
132            // get the gbean
133            Object instance = null;
134            try {
135                instance = kernel.getGBean(abstractName);
136            } catch (GBeanNotFoundException e) {
137                throw (NamingException)new NamingException("GBean not found: " + abstractName).initCause(e);
138            }
139    
140            // preprocess the instance
141            instance = preprocessVaue(abstractName, instance);
142    
143            addBinding(abstractName, instance);
144        }
145    
146        private synchronized void addBinding(AbstractName abstractName, Object value) throws NamingException {
147            if (bindings.isEmpty()) {
148                context.bind(name, value);
149            }
150            bindings.put(abstractName, value);
151        }
152    
153        /**
154         * Unbinds the specified gbean.
155         *
156         * @param abstractName the abstract name of the gbean to unbind
157         */
158        protected synchronized void removeBinding(AbstractName abstractName) {
159            Map.Entry entry = first(bindings);
160            if (entry != null && entry.getKey().equals(abstractName)) {
161                Object oldValue = bindings.remove(abstractName);
162                entry = first(bindings);
163                if (entry != null) {
164                    Object newAbstractName = entry.getValue();
165                    Object newValue = entry.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    }