001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with 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    
019    package org.apache.hadoop.util;
020    
021    import org.apache.commons.logging.*;
022    
023    import org.apache.hadoop.conf.Configuration;
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.List;
029    import java.util.concurrent.Executor;
030    import java.util.concurrent.Executors;
031    
032    /**
033     * Provides convenience functions for dispatching calls through
034     * to plugins registered with a class. Classes that wish to provide
035     * plugin interfaces should use this class to load the plugin list
036     * from the Configuration and to dispatch calls to the loaded instances.
037     *
038     * Calls dispatched through this class are performed on a second thread
039     * so as to not block execution of the plugged service.
040     */
041    public class PluginDispatcher<T extends ServicePlugin> {
042      public static final Log LOG = LogFactory.getLog(PluginDispatcher.class.getName());
043    
044      private final List<T> plugins;
045      private Executor executor;
046    
047      /**
048       * Load a PluginDispatcher from the given Configuration. The start()
049       * callback will not be automatically called.
050       *
051       * @param conf the Configuration from which to load
052       * @param key the configuration key that lists class names to instantiate
053       * @param clazz the class or interface from which plugins must extend
054       */
055      public static <X extends ServicePlugin> PluginDispatcher<X> createFromConfiguration(
056        Configuration conf, String key, Class<X> clazz) {
057        List<X> plugins = new ArrayList<X>();
058        try {
059          plugins.addAll(conf.getInstances(key, clazz));
060        } catch (Throwable t) {
061          LOG.warn("Unable to load "+key+" plugins");
062        }
063        return new PluginDispatcher<X>(plugins);
064      }
065    
066      PluginDispatcher(Collection<T> plugins) {
067        this.plugins = Collections.synchronizedList(new ArrayList<T>(plugins));
068        executor = Executors.newSingleThreadExecutor();
069      }
070    
071      PluginDispatcher(Collection<T> plugins, Executor executor) {
072        this.plugins = Collections.synchronizedList(new ArrayList<T>(plugins));
073        this.executor = executor;
074      }
075    
076      /**
077       * Dispatch a call to all active plugins.
078       *
079       * Exceptions will be caught and logged at WARN level.
080       *
081       * @param callback a function which will run once for each plugin, with
082       * that plugin as the argument
083       */
084      public void dispatchCall(final SingleArgumentRunnable<T> callback) {
085        executor.execute(new Runnable() {
086          public void run() {
087            for (T plugin : plugins) {
088              try {
089                callback.run(plugin);
090              } catch (Throwable t) {
091                LOG.warn("Uncaught exception dispatching to plugin " + plugin, t);
092              }
093            }
094          }});
095      }
096    
097      /**
098       * Dispatches the start(...) hook common to all ServicePlugins. This
099       * also automatically removes any plugin that throws an exception while
100       * attempting to start.
101       *
102       * @param plugPoint passed to ServicePlugin.start()
103       */
104      public void dispatchStart(final Object plugPoint) {
105        dispatchCall(
106          new SingleArgumentRunnable<T>() {
107            public void run(T p) {
108              try {
109                p.start(plugPoint);
110              } catch (Throwable t) {
111                LOG.error("ServicePlugin " + p + " could not be started. " +
112                          "Removing from future callbacks.", t);
113                plugins.remove(p);
114              }
115            }
116          });
117      }
118    
119      /**
120       * Convenience function for dispatching the stop() hook common to all
121       * ServicePlugins.
122       */
123      public void dispatchStop() {
124        dispatchCall(
125          new SingleArgumentRunnable<T>() {
126            public void run(T p) {
127              try {
128                p.stop();
129              } catch (Throwable t) {
130                LOG.warn("ServicePlugin " + p + " could not be stopped", t);
131              }
132            }
133          });
134      }
135    }