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 java.util.HashSet;
020    import java.util.LinkedHashMap;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import javax.naming.Context;
025    import javax.naming.NamingException;
026    
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.apache.geronimo.gbean.AbstractName;
030    import org.apache.geronimo.gbean.AbstractNameQuery;
031    import org.apache.geronimo.gbean.GBeanInfo;
032    import org.apache.geronimo.gbean.GBeanInfoBuilder;
033    import org.apache.geronimo.gbean.GBeanLifecycle;
034    import org.apache.geronimo.kernel.GBeanNotFoundException;
035    import org.apache.geronimo.kernel.Kernel;
036    import org.apache.geronimo.kernel.lifecycle.LifecycleAdapter;
037    import org.apache.geronimo.kernel.lifecycle.LifecycleListener;
038    
039    /**
040     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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<AbstractName, Object> bindings = new LinkedHashMap<AbstractName, Object>();
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<AbstractName> set = kernel.listGBeans(abstractNameQuery);
063            for (AbstractName abstractName : set) {
064                try {
065                    if (kernel.isRunning(abstractName)) {
066                        addBinding(abstractName);
067                    }
068                } catch (NamingException e) {
069                    log.error("Error adding binding for " + abstractName, e);
070                }
071            }
072    
073        }
074    
075        public void doStop() {
076            destroy();
077        }
078    
079        public void doFail() {
080            destroy();
081        }
082    
083        private synchronized void destroy() {
084            kernel.getLifecycleMonitor().removeLifecycleListener(listener);
085            Set<AbstractName> abstractNames = new HashSet<AbstractName>(bindings.keySet());
086            for (AbstractName abstractName : abstractNames) {
087                removeBinding(abstractName);
088            }
089            bindings.clear();
090        }
091    
092        private class GBeanLifecycleListener extends LifecycleAdapter {
093            public void running(AbstractName abstractName) {
094                try {
095                    addBinding(abstractName);
096                } catch (NamingException e) {
097                    log.error("Error adding binding for " + abstractName);
098                }
099            }
100    
101            public void stopping(AbstractName abstractName) {
102                removeBinding(abstractName);
103            }
104    
105            public void stopped(AbstractName abstractName) {
106                removeBinding(abstractName);
107            }
108    
109            public void failed(AbstractName abstractName) {
110                removeBinding(abstractName);
111            }
112    
113            public void unloaded(AbstractName abstractName) {
114                removeBinding(abstractName);
115            }
116        }
117    
118        /**
119         * Binds the specified gbean.  This method uses createBindingName and preprocessValue before binding the object.
120         *
121         * @param abstractName the abstract name of the gbean to bind
122         * @throws NamingException if an error occurs during binding
123         */
124        protected synchronized void addBinding(AbstractName abstractName) throws NamingException {
125            if (bindings.containsKey(abstractName)) {
126                // previously bound
127                return;
128            }
129    
130            // get the gbean
131            Object instance;
132            try {
133                instance = kernel.getGBean(abstractName);
134            } catch (GBeanNotFoundException e) {
135                throw (NamingException)new NamingException("GBean not found: " + abstractName).initCause(e);
136            }
137    
138            // preprocess the instance
139            instance = preprocessVaue(abstractName, instance);
140    
141            addBinding(abstractName, instance);
142        }
143    
144        private synchronized void addBinding(AbstractName abstractName, Object value) throws NamingException {
145            if (bindings.isEmpty()) {
146                context.bind(name, value);
147            }
148            bindings.put(abstractName, value);
149        }
150    
151        /**
152         * Unbinds the specified gbean.
153         *
154         * @param abstractName the abstract name of the gbean to unbind
155         */
156        protected synchronized void removeBinding(AbstractName abstractName) {
157            Map.Entry entry = first(bindings);
158            if (entry != null && entry.getKey().equals(abstractName)) {
159                Object oldValue = bindings.remove(abstractName);
160                entry = first(bindings);
161                if (entry != null) {
162                    Object newAbstractName = entry.getValue();
163                    Object newValue = entry.getValue();
164                    try {
165                        context.rebind(name, newValue);
166                    } catch (NamingException e) {
167                        boolean unbound = unbind(abstractName, oldValue);
168                        // avoid double logging
169                        if (unbound) log.error("Unable to rebind binding " + name + " to " + newAbstractName);
170                    }
171                } else {
172                    unbind(abstractName, oldValue);
173                }
174            } else {
175                bindings.remove(abstractName);
176            }
177        }
178    
179        private boolean unbind(AbstractName abstractName, Object value) {
180            // first check if we are still bound
181            try {
182                if (context.lookup(name) != value) {
183                    return true;
184                }
185            } catch (NamingException ignored) {
186                // binding doesn't exist
187                return true;
188            }
189    
190            try {
191                context.unbind(name);
192                return true;
193            } catch (NamingException e1) {
194                log.error("Unable to remove binding " + name + " to " + abstractName, e1);
195            }
196            return false;
197        }
198    
199        private static Map.Entry first(LinkedHashMap map) {
200            if (map.isEmpty()) return null;
201            return (Map.Entry) map.entrySet().iterator().next();
202        }
203    
204        /**
205         * Preprocess the value before it is bound.  This is usefult for wrapping values with reference objects.
206         * By default, this method simply return the value.
207         *
208         * @param abstractName the abstract name of the gbean to bind
209         * @param value        the gbean instance
210         * @return the value to bind
211         */
212        protected Object preprocessVaue(AbstractName abstractName, Object value) throws NamingException {
213            return value;
214        }
215    
216        public static final GBeanInfo GBEAN_INFO;
217    
218        public static GBeanInfo getGBeanInfo() {
219            return GBEAN_INFO;
220        }
221    
222        static {
223            GBeanInfoBuilder builder = GBeanInfoBuilder.createStatic(GBeanBinding.class, "GBeanBinding");
224            builder.addReference("Context", Context.class);
225            builder.addAttribute("name", String.class, true);
226            builder.addAttribute("abstractNameQuery", AbstractNameQuery.class, true);
227            builder.setConstructor(new String[]{"Context", "name", "abstractNameQuery", "kernel"});
228            GBEAN_INFO = builder.getBeanInfo();
229        }
230    }