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.service;
020    
021    import java.util.ArrayList;
022    import java.util.List;
023    
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    import org.apache.hadoop.classification.InterfaceAudience.Public;
027    import org.apache.hadoop.classification.InterfaceStability.Evolving;
028    
029    /**
030     * This class contains a set of methods to work with services, especially
031     * to walk them through their lifecycle.
032     */
033    @Public
034    @Evolving
035    public final class ServiceOperations {
036      private static final Log LOG = LogFactory.getLog(AbstractService.class);
037    
038      private ServiceOperations() {
039      }
040    
041      /**
042       * Stop a service.
043       * <p/>Do nothing if the service is null or not
044       * in a state in which it can be/needs to be stopped.
045       * <p/>
046       * The service state is checked <i>before</i> the operation begins.
047       * This process is <i>not</i> thread safe.
048       * @param service a service or null
049       */
050      public static void stop(Service service) {
051        if (service != null) {
052          service.stop();
053        }
054      }
055    
056      /**
057       * Stop a service; if it is null do nothing. Exceptions are caught and
058       * logged at warn level. (but not Throwables). This operation is intended to
059       * be used in cleanup operations
060       *
061       * @param service a service; may be null
062       * @return any exception that was caught; null if none was.
063       */
064      public static Exception stopQuietly(Service service) {
065        return stopQuietly(LOG, service);
066      }
067    
068      /**
069       * Stop a service; if it is null do nothing. Exceptions are caught and
070       * logged at warn level. (but not Throwables). This operation is intended to
071       * be used in cleanup operations
072       *
073       * @param log the log to warn at
074       * @param service a service; may be null
075       * @return any exception that was caught; null if none was.
076       * @see ServiceOperations#stopQuietly(Service)
077       */
078      public static Exception stopQuietly(Log log, Service service) {
079        try {
080          stop(service);
081        } catch (Exception e) {
082          log.warn("When stopping the service " + service.getName()
083                   + " : " + e,
084                   e);
085          return e;
086        }
087        return null;
088      }
089    
090    
091      /**
092       * Class to manage a list of {@link ServiceStateChangeListener} instances,
093       * including a notification loop that is robust against changes to the list
094       * during the notification process.
095       */
096      public static class ServiceListeners {
097        /**
098         * List of state change listeners; it is final to guarantee
099         * that it will never be null.
100         */
101        private final List<ServiceStateChangeListener> listeners =
102          new ArrayList<ServiceStateChangeListener>();
103    
104        /**
105         * Thread-safe addition of a new listener to the end of a list.
106         * Attempts to re-register a listener that is already registered
107         * will be ignored.
108         * @param l listener
109         */
110        public synchronized void add(ServiceStateChangeListener l) {
111          if(!listeners.contains(l)) {
112            listeners.add(l);
113          }
114        }
115    
116        /**
117         * Remove any registration of a listener from the listener list.
118         * @param l listener
119         * @return true if the listener was found (and then removed)
120         */
121        public synchronized boolean remove(ServiceStateChangeListener l) {
122          return listeners.remove(l);
123        }
124    
125        /**
126         * Reset the listener list
127         */
128        public synchronized void reset() {
129          listeners.clear();
130        }
131    
132        /**
133         * Change to a new state and notify all listeners.
134         * This method will block until all notifications have been issued.
135         * It caches the list of listeners before the notification begins,
136         * so additions or removal of listeners will not be visible.
137         * @param service the service that has changed state
138         */
139        public void notifyListeners(Service service) {
140          //take a very fast snapshot of the callback list
141          //very much like CopyOnWriteArrayList, only more minimal
142          ServiceStateChangeListener[] callbacks;
143          synchronized (this) {
144            callbacks = listeners.toArray(new ServiceStateChangeListener[listeners.size()]);
145          }
146          //iterate through the listeners outside the synchronized method,
147          //ensuring that listener registration/unregistration doesn't break anything
148          for (ServiceStateChangeListener l : callbacks) {
149            l.stateChanged(service);
150          }
151        }
152      }
153    
154    }