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 org.apache.hadoop.classification.InterfaceAudience.Public;
022    import org.apache.hadoop.classification.InterfaceStability.Evolving;
023    
024    /**
025     * Implements the service state model.
026     */
027    @Public
028    @Evolving
029    public class ServiceStateModel {
030    
031      /**
032       * Map of all valid state transitions
033       * [current] [proposed1, proposed2, ...]
034       */
035      private static final boolean[][] statemap =
036        {
037          //                uninited inited started stopped
038          /* uninited  */    {false, true,  false,  true},
039          /* inited    */    {false, true,  true,   true},
040          /* started   */    {false, false, true,   true},
041          /* stopped   */    {false, false, false,  true},
042        };
043    
044      /**
045       * The state of the service
046       */
047      private volatile Service.STATE state;
048    
049      /**
050       * The name of the service: used in exceptions
051       */
052      private String name;
053    
054      /**
055       * Create the service state model in the {@link Service.STATE#NOTINITED}
056       * state.
057       */
058      public ServiceStateModel(String name) {
059        this(name, Service.STATE.NOTINITED);
060      }
061    
062      /**
063       * Create a service state model instance in the chosen state
064       * @param state the starting state
065       */
066      public ServiceStateModel(String name, Service.STATE state) {
067        this.state = state;
068        this.name = name;
069      }
070    
071      /**
072       * Query the service state. This is a non-blocking operation.
073       * @return the state
074       */
075      public Service.STATE getState() {
076        return state;
077      }
078    
079      /**
080       * Query that the state is in a specific state
081       * @param proposed proposed new state
082       * @return the state
083       */
084      public boolean isInState(Service.STATE proposed) {
085        return state.equals(proposed);
086      }
087    
088      /**
089       * Verify that that a service is in a given state.
090       * @param expectedState the desired state
091       * @throws ServiceStateException if the service state is different from
092       * the desired state
093       */
094      public void ensureCurrentState(Service.STATE expectedState) {
095        if (state != expectedState) {
096          throw new ServiceStateException(name+ ": for this operation, the " +
097                                          "current service state must be "
098                                          + expectedState
099                                          + " instead of " + state);
100        }
101      }
102    
103      /**
104       * Enter a state -thread safe.
105       *
106       * @param proposed proposed new state
107       * @return the original state
108       * @throws ServiceStateException if the transition is not permitted
109       */
110      public synchronized Service.STATE enterState(Service.STATE proposed) {
111        checkStateTransition(name, state, proposed);
112        Service.STATE oldState = state;
113        //atomic write of the new state
114        state = proposed;
115        return oldState;
116      }
117    
118      /**
119       * Check that a state tansition is valid and
120       * throw an exception if not
121       * @param name name of the service (can be null)
122       * @param state current state
123       * @param proposed proposed new state
124       */
125      public static void checkStateTransition(String name,
126                                              Service.STATE state,
127                                              Service.STATE proposed) {
128        if (!isValidStateTransition(state, proposed)) {
129          throw new ServiceStateException(name + " cannot enter state "
130                                          + proposed + " from state " + state);
131        }
132      }
133    
134      /**
135       * Is a state transition valid?
136       * There are no checks for current==proposed
137       * as that is considered a non-transition.
138       *
139       * using an array kills off all branch misprediction costs, at the expense
140       * of cache line misses.
141       *
142       * @param current current state
143       * @param proposed proposed new state
144       * @return true if the transition to a new state is valid
145       */
146      public static boolean isValidStateTransition(Service.STATE current,
147                                                   Service.STATE proposed) {
148        boolean[] row = statemap[current.getValue()];
149        return row[proposed.getValue()];
150      }
151    
152      /**
153       * return the state text as the toString() value
154       * @return the current state's description
155       */
156      @Override
157      public String toString() {
158        return (name.isEmpty() ? "" : ((name) + ": "))
159                + state.toString();
160      }
161    
162    }