Author: stevel
Date: Wed May 27 10:58:05 2009
New Revision: 779104

URL: http://svn.apache.org/viewvc?rev=779104&view=rev
Log:
HADOOP-3628 creating updated branch

Added:
    
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/MockService.java
    
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/Service.java

Added: 
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/MockService.java
URL: 
http://svn.apache.org/viewvc/hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/MockService.java?rev=779104&view=auto
==============================================================================
--- 
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/MockService.java
 (added)
+++ 
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/MockService.java
 Wed May 27 10:58:05 2009
@@ -0,0 +1,156 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.util;
+
+import org.apache.hadoop.conf.Configuration;
+
+import java.io.IOException;
+
+/**
+ * A mock service that can be set to fail in different parts of its lifecycle,
+ * and which counts the number of times its inner classes changed state.
+ */
+
+public class MockService extends Service {
+
+  /**
+   * Build from an empty configuration
+   */
+  public MockService() {
+    super(new Configuration());
+  }
+
+  /**
+   * Build from a configuration file
+   * @param conf
+   */
+  public MockService(Configuration conf) {
+    super(conf);
+  }
+
+  private boolean failOnStart, failOnPing, failOnClose;
+  private boolean goLiveInStart = true;
+  private boolean closed = true;
+  private volatile int stateChangeCount = 0;
+  private volatile int pingCount = 0;
+
+  public void setFailOnStart(boolean failOnStart) {
+    this.failOnStart = failOnStart;
+  }
+
+  public void setFailOnPing(boolean failOnPing) {
+    this.failOnPing = failOnPing;
+  }
+
+  public void setGoLiveInStart(boolean goLiveInStart) {
+    this.goLiveInStart = goLiveInStart;
+  }
+
+  public void setFailOnClose(boolean failOnClose) {
+    this.failOnClose = failOnClose;
+  }
+
+  public boolean isClosed() {
+    return closed;
+  }
+
+  /**
+   * Go live
+   *
+   * @throws ServiceStateException if we were not in a state to do so
+   */
+  public void goLive() throws ServiceStateException {
+    enterLiveState();
+  }
+
+  /**
+   * {...@inheritdoc}
+   * @throws IOException  if {...@link #failOnStart is set}
+   */
+  @Override
+  protected void innerStart() throws IOException {
+    if (failOnStart) {
+      throw new MockServiceException("failOnStart");
+    }
+    if (goLiveInStart) {
+      goLive();
+    }
+  }
+
+  /**
+   * {...@inheritdoc}
+   * @throws IOException if {...@link #failOnPing is set} @param status
+   */
+  @Override
+  protected void innerPing(ServiceStatus status) throws IOException {
+    pingCount++;
+    if (failOnPing) {
+      throw new MockServiceException("failOnPing");
+    }
+  }
+
+  /**
+   * {...@inheritdoc}
+   *
+   * @throws IOException if {...@link #failOnClose} is true
+   */
+  protected void innerClose() throws IOException {
+    closed = true;
+    if (failOnClose) {
+      throw new MockServiceException("failOnClose");
+    }
+  }
+
+  /**
+   * {...@inheritdoc}
+   */
+  @Override
+  protected void onStateChange(ServiceState oldState,
+                               ServiceState newState) {
+    super.onStateChange(oldState, newState);
+    stateChangeCount++;
+  }
+
+  /**
+   * {...@inheritdoc}
+   *
+   * A public method do change state
+   */
+  public void changeState(ServiceState state)
+          throws ServiceStateException {
+    setServiceState(state);
+  }
+
+  public int getStateChangeCount() {
+    return stateChangeCount;
+  }
+
+  public int getPingCount() {
+    return pingCount;
+  }
+
+  /**
+   * An exception to indicate we have triggered a mock event
+   */
+  static class MockServiceException extends IOException {
+
+    private MockServiceException(String message) {
+      super(message);
+    }
+  }
+}

Added: 
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/Service.java
URL: 
http://svn.apache.org/viewvc/hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/Service.java?rev=779104&view=auto
==============================================================================
--- 
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/Service.java 
(added)
+++ 
hadoop/core/branches/HADOOP-3628-2/src/core/org/apache/hadoop/util/Service.java 
Wed May 27 10:58:05 2009
@@ -0,0 +1,1006 @@
+/*
+ * Copyright  2008 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.util;
+
+import org.apache.hadoop.conf.Configured;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Closeable;
+import java.util.Date;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is the base class for services that can be deployed. A service is any
+ * Hadoop class that has a standard lifecycle
+ *
+ * The lifecycle of a Service is:
+ *
+ * <ol>
+ *
+ * <li>Component is Created, enters the {...@link ServiceState#CREATED} state.
+ * This happens in the constructor. </li>
+ *
+ * <li>Component is started
+ * through a call to {...@link Service#start()} ()}. If successful, it enters 
the
+ * {...@link ServiceState#STARTED} state. If not, it enters the {...@link
+ * ServiceState#FAILED} state. </li>
+ *
+ * <li>Once the component considers itself
+ * fully started, it enters the {...@link ServiceState#LIVE} state. This 
implies it
+ * is providing a service to external callers. </li>
+ *
+ * </ol>
+ *
+ * From any state, the service can be terminated/closed through a call to
+ * {...@link Service#close()}, which may throw an {...@link IOException}, or
+ * {...@link Service#closeQuietly()}, which catched and logs any such 
exception.
+ * These are idempotent calls, and will place the service in the
+ * {...@link ServiceState#CLOSED}, terminated  state, after which
+ * it can no longer be used.
+ *
+ * To implement a Service.
+ *
+ * <ol>
+ *
+ * <li>Subclass this class</li>
+ *
+ * <li>Avoid doing any initialization/startup in the constructors, as this
+ * breaks the lifecycle and prevents subclassing. </li>
+ *
+ * <li>If the service wishes to declare itself as having failed, so that
+ * {...@link #ping()} operations automatically fail, call
+ * {...@link #enterFailedState(Throwable)} to enter the failed state.</li>
+ *
+ * <li>Override the {...@link #innerStart()} method to start the service, 
including
+ * starting any worker threads.</li>
+ *
+ * <li>In the {...@link #innerStart()} method, if the service is immediately 
live
+ * to external callers, call {...@link #enterLiveState()} to mark the service 
as
+ * live.</li>
+
+ * <li>If startup is performed in separate threads, and includes bootstrap 
work,
+ * call the  {...@link #enterLiveState()} in the separate thread <i>when the
+ * service is ready</i></li>
+ *
+ * <li>Override {...@link #innerPing(ServiceStatus)} with any health checks 
that a service
+ * can perform to check that it is still "alive". These should be short lasting
+ * and non-side effecting. Simple checks for valid data structures and live
+ * worker threads are good examples. When the service thinks that something
+ * has failed, throw an IOException with a meaningful error message!
+ * </li>
+ *
+ * <li>Override {...@link #innerClose()} to perform all shutdown logic.
+ * Be robust here and shut down cleanly even if the service did not start up
+ * completely. Do not assume all fields are non-null</li>
+ *
+ * You should not need to worry about making these overridden methods
+ * synchronized, as they are only called when a service has entered a specific
+ * state -which is synchronized. Except for {...@link 
#innerPing(ServiceStatus)} ,
+ * each method will only be called at most once in the life of a service 
instance.
+ * However, because findbugs can flag synchronization warnings, it is often
+ * simplest and safest to mark the innerX operations as synchronized.
+ */
+
+public abstract class Service extends Configured implements Closeable {
+
+  private static final Log LOG = LogFactory.getLog(Service.class);
+
+  /**
+   * The initial state of a service is {...@link ServiceState#CREATED}
+   */
+  private volatile ServiceState serviceState = ServiceState.CREATED;
+
+  /**
+   * when did the state change?
+   */
+  private volatile Date lastStateChange = new Date();
+
+  /**
+   * A root cause for failure. May be null.
+   */
+  private Throwable failureCause;
+
+  /**
+   * Error string included in {...@link ServiceStateException} exceptions
+   * when an operation is applied to a service that is not in the correct
+   * state for it.
+   * value: {...@value}
+   */
+  public static final String ERROR_WRONG_STATE = " is in the wrong state.";
+
+  /**
+   * Error string included in {...@link ServiceStateException} exceptions
+   * when a service with a null configuration is started
+   * value: {...@value}
+   */
+  public static final String ERROR_NO_CONFIGURATION
+          = "Cannot initialize when unconfigured";
+
+  /**
+   * Construct a service with no configuration; one must be called with 
{...@link
+   * #setConf(Configuration)} before the service is started
+   */
+  protected Service() {
+  }
+
+  /**
+   * Construct a Configured service
+   *
+   * @param conf the configuration
+   */
+  protected Service(Configuration conf) {
+    super(conf);
+  }
+
+  /**
+   * Start any work (usually in separate threads).
+   *
+   * When completed, the service will be in the {...@link ServiceState#STARTED}
+   * state, or may have already transited to the {...@link ServiceState#LIVE}
+   * state
+   *
+   * Subclasses must implement their work in {...@link #innerStart()}, leaving 
the
+   * start() method to manage state checks and changes.
+   *
+   * @throws IOException           for any failure
+   * @throws ServiceStateException when the service is not in a state from 
which
+   *                               it can enter this state.
+   */
+  public void start() throws IOException {
+    synchronized (this) {
+      //this request is idempotent on either live or starting states; either
+      //state is ignored
+      ServiceState currentState = getServiceState();
+      if (currentState == ServiceState.LIVE ||
+              currentState == ServiceState.STARTED) {
+        return;
+      }
+      if (getConf() == null) {
+        throw new ServiceStateException(ERROR_NO_CONFIGURATION,
+                getServiceState());
+      }
+      //check and change states
+      enterState(ServiceState.STARTED);
+    }
+    try {
+      innerStart();
+    } catch (IOException e) {
+      enterFailedState(e);
+      throw e;
+    }
+  }
+
+  /**
+   * Ping: checks that a component considers itself live.
+   *
+   * This may trigger a health check in which the service probes its
+   * constituent parts to verify that they are themselves live.
+   * The base implementation considers any state other than
+   * {...@link ServiceState#FAILED} and {...@link ServiceState#CLOSED}
+   * to be valid, so it is OK to ping a
+   * component that is still starting up. However, in such situations, the 
inner
+   * ping health tests are skipped, because they are generally irrelvant.
+   *
+   * Subclasses should not normally override this method, but instead override
+   * {...@link #innerPing(ServiceStatus)} with extra health checks that will 
only
+   * be called when a system is actually live.
+   * @return the current service state.
+   * @throws IOException           for any ping failure
+   * @throws ServiceStateException if the component is in a wrong state.
+   */
+  public ServiceStatus ping() throws IOException {
+    ServiceStatus status = new ServiceStatus(this);
+    ServiceState state = status.getState();
+    if (state == ServiceState.LIVE) {
+      try {
+        innerPing(status);
+      } catch (Throwable thrown) {
+        //TODO: what happens whenthe ping() returns >0 causes of failure but 
+        //doesn't throw an exception -this method will not get called. Is 
+        //that what we want?
+        status = onInnerPingFailure(status, thrown);
+      }
+    } else {
+      //ignore the ping
+      LOG.debug("ignoring ping request while in state " + state);
+      //but tack on any non-null failure cause, which may be a valid value
+      //in FAILED or TERMINATED states.
+      status.addThrowable(getFailureCause());
+    }
+    return status;
+  }
+
+  /**
+   * This is an override point for services -handle failure of the inner
+   * ping operation.
+   * The base implementation calls {...@link #enterFailedState(Throwable)} and 
then
+   * updates the service status with the (new) state and the throwable
+   * that was caught.
+   * @param currentStatus the current status structure
+   * @param thrown the exception from the failing ping.
+   * @return an updated service status structure.
+   * @throws IOException for IO problems
+   */
+  protected ServiceStatus onInnerPingFailure(ServiceStatus currentStatus,
+                                             Throwable thrown) 
+          throws IOException {
+    //something went wrong
+    //mark as failed
+    //TODO: don't enter failed state on a failing ping? Just report the event
+    //to the caller?
+    enterFailedState(thrown);
+    //update the state
+    currentStatus.updateState(this);
+    currentStatus.addThrowable(thrown);
+    //and return the exception.
+    return currentStatus;
+  }
+
+  /**
+   * Convert any exception to an {...@link IOException}
+   * If it already is an IOException, the exception is
+   * returned as is. If it is anything else, it is wrapped, with
+   * the original message retained.
+   * @param thrown the exception to forward
+   * @return an IOException representing or containing the forwarded exception
+   */
+  protected IOException forwardAsIOException(Throwable thrown) {
+    IOException newException;
+    if(thrown instanceof IOException) {
+      newException = (IOException) thrown;
+    } else {
+      IOException ioe = new IOException(thrown.toString());
+      ioe.initCause(thrown);
+      newException = ioe;
+    }
+    return newException;
+  }
+
+
+  /**
+   * Test for a service being in the {...@link ServiceState#LIVE} or {...@link
+   * ServiceState#STARTED}
+   *
+   * @return true if the service is in one of the two states.
+   */
+  public final boolean isRunning() {
+    ServiceState currentState = getServiceState();
+    return currentState == ServiceState.STARTED
+            || currentState == ServiceState.LIVE;
+  }
+
+  /**
+   * Shut down. This must be idempotent and turn errors into log/warn events 
-do
+   * your best to clean up even in the face of adversity. This method should be
+   * idempotent; if already terminated, return. Similarly, do not fail if the
+   * component never actually started.
+   *
+   * The implementation calls {...@link #close()} and then
+   * {...@link #logExceptionDuringQuietClose(Throwable)} if that method throws
+   * any exception.
+   */
+  public final void closeQuietly() {
+    try {
+      close();
+    } catch (Throwable e) {
+      logExceptionDuringQuietClose(e);
+    }
+  }
+
+  /**
+   * Closes this service. Subclasses are free to throw an exception, but
+   * they are expected to make a best effort attempt to close the service
+   * down as thoroughly as possible.
+   *
+   * @throws IOException if an I/O error occurs
+   */
+  public void close() throws IOException {
+    if (enterState(ServiceState.CLOSED)) {
+      innerClose();
+    }
+  }
+
+  /**
+   * This is a method called when exceptions are being logged and swallowed
+   * during termination. It logs the event at the error level.
+   *
+   * Subclasses may override this to do more advanced error handling/logging.
+   *
+   * @param thrown whatever was thrown
+   */
+  protected void logExceptionDuringQuietClose(Throwable thrown) {
+    LOG.error("Exception during termination: " + thrown,
+            thrown);
+  }
+
+  /**
+   * This method is designed for overriding, with subclasses implementing
+   * startup logic inside it. It is only called when the component is entering
+   * the running state; and will be called once only.
+   *
+   * When the work in here is completed, the component should set the service
+   * state to {...@link ServiceState#LIVE} to indicate the service is now live.
+   *
+   * @throws IOException for any problem.
+   */
+  protected void innerStart() throws IOException {
+  }
+
+
+  /**
+   * This method is designed for overriding, with subclasses implementing 
health
+   * tests inside it.
+   *
+   * It is invoked whenever the component is called with {...@link 
Service#ping()}
+   * and the call is not rejected.
+   * @param status the service status, which can be updated
+   * @throws IOException for any problem.
+   */
+  protected void innerPing(ServiceStatus status) throws IOException {
+  }
+
+  /**
+   * This method is designed for overriding, with subclasses implementing
+   * termination logic inside it.
+   *
+   * It is only called when the component is entering the closed state; and
+   * will be called once only.
+   *
+   * @throws IOException exceptions which will be logged
+   */
+  protected void innerClose() throws IOException {
+
+  }
+
+  /**
+   * Get the current state of the service.
+   *
+   * @return the lifecycle state
+   */
+  public final ServiceState getServiceState() {
+    return serviceState;
+  }
+
+  /**
+   * This is the state transition graph represented as some nested switches.
+   * @return true if the transition is valid. For all states, the result when
+   * oldState==newState is false: that is not a transition, after all.
+   * @param oldState the old state of the service
+   * @param newState the new state
+   */
+  protected boolean isValidStateTransition(ServiceState oldState,
+                                           ServiceState newState) {
+    switch(oldState) {
+      case CREATED:
+        switch(newState) {
+          case STARTED:
+          case FAILED:
+          case CLOSED:
+            return true;
+          default:
+            return false;
+        }
+      case STARTED:
+        switch (newState) {
+          case LIVE:
+          case FAILED:
+          case CLOSED:
+            return true;
+          default:
+            return false;
+        }
+      case LIVE:
+        switch (newState) {
+          case STARTED:
+          case FAILED:
+          case CLOSED:
+            return true;
+          default:
+            return false;
+        }
+      case UNDEFINED:
+        //if we don't know where we were before (very, very unlikely), then
+        //let's get out of it
+        return true;
+      case FAILED:
+        //failure can only enter closed state
+        return newState == ServiceState.CLOSED;
+      case CLOSED:
+        //This is the end state. There is no exit.
+      default:
+        return false;
+    }
+  }
+
+  /**
+  * Set the service state.
+  * If there is a change in state, the {...@link #lastStateChange} timestamp
+  * is updated and the {...@link #onStateChange(ServiceState, ServiceState)} 
event
+  * is invoked.
+  * @param serviceState the new state
+  */
+  protected final void setServiceState(ServiceState serviceState) {
+    ServiceState oldState;
+    synchronized (this) {
+      oldState = this.serviceState;
+      this.serviceState = serviceState;
+    }
+    if (oldState != serviceState) {
+      lastStateChange = new Date();
+      onStateChange(oldState, serviceState);
+    }
+  }
+
+  /**
+   * Override point - a method called whenever there is a state change.
+   *
+   * The base class logs the event.
+   *
+   * @param oldState existing state
+   * @param newState new state.
+   */
+  protected void onStateChange(ServiceState oldState,
+                               ServiceState newState) {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("State Change: " + toString()
+              + " transitioned from state " + oldState + " to " + newState);
+    }
+  }
+
+  /**
+   * Enter a new state if that is permitted from the current state.
+   * Does nothing if we are in that state; throws an exception if the
+   * state transition is not permitted
+   * @param  newState  the new state
+   * @return true if the service transitioned into this state, that is, it was
+   *         not already in the state
+   * @throws ServiceStateException if the service is not in either state
+   */
+  protected final synchronized boolean enterState(ServiceState newState)
+          throws ServiceStateException {
+    return enterState(getServiceState(), newState);
+  }
+
+  /**
+   * Check that a service is in a required entry state, or already in the
+   * desired exit state.
+   *
+   * @param entryState the state that is needed. If set to {...@link
+   *                   ServiceState#UNDEFINED} then the entry state is not
+   *                   checked.
+   * @param exitState  the state that is desired when we exit
+   * @return true if the service transitioned into this state, that is, it was
+   *         not already in the state
+   * @throws ServiceStateException if the service is not in either state
+   */
+  protected final synchronized boolean enterState(ServiceState entryState,
+                                            ServiceState exitState)
+          throws ServiceStateException {
+    ServiceState currentState = getServiceState();
+    if (currentState == exitState) {
+      return false;
+    }
+    validateStateTransition(entryState, exitState);
+    setServiceState(exitState);
+    return true;
+  }
+
+  /**
+   * Check that the state transition is valid
+   * @param entryState the entry state
+   * @param exitState the exit state 
+   * @throws ServiceStateException if the state transition is not allowed
+   */
+  protected final void validateStateTransition(ServiceState entryState,
+                                         ServiceState exitState)
+          throws ServiceStateException {
+    if(!isValidStateTransition(entryState, exitState)) {
+      throw new ServiceStateException(toString()
+              + ERROR_WRONG_STATE
+              + " The service cannot move from the state " + entryState
+              + "to the state " + exitState,
+              entryState);
+    }
+  }
+
+
+  /**
+   * Verify that a service is in a specific state
+   *
+   * @param state the state that is required.
+   * @throws ServiceStateException if the service is in the wrong state
+   */
+  public final void verifyServiceState(ServiceState state)
+          throws ServiceStateException {
+    verifyState(getServiceState(), state, ServiceState.UNDEFINED);
+  }
+
+  /**
+   * Verify that a service is in either of two specific states
+   *
+   * @param expected  the state that is expected.
+   * @param expected2 a second state, which can be left at {...@link
+   *                  ServiceState#UNDEFINED} for "do not check this"
+   * @throws ServiceStateException if the service is in the wrong state
+   */
+  public final void verifyServiceState(ServiceState expected, ServiceState 
expected2)
+          throws ServiceStateException {
+    verifyState(getServiceState(), expected, expected2);
+  }
+
+  /**
+   * Internal state verification test
+   *
+   * @param currentState the current state
+   * @param expected     the state that is expected.
+   * @param expected2    a second state, which can be left at {...@link
+   *                     ServiceState#UNDEFINED} for "do not check this"
+   * @throws ServiceStateException if the service is in the wrong state
+   */
+  protected final void verifyState(ServiceState currentState,
+                             ServiceState expected,
+                             ServiceState expected2)
+          throws ServiceStateException {
+    boolean expected2defined = expected2 != ServiceState.UNDEFINED;
+    if (!(currentState == expected ||
+            (expected2defined && currentState == expected2))) {
+      throw new ServiceStateException(toString()
+              + ERROR_WRONG_STATE
+              + " Expected " + expected
+              + (expected2defined ? (" or " + expected2) : " ")
+              + " but the actual state is " + currentState,
+              currentState);
+    }
+  }
+
+  /**
+   * Helper method to enter the {...@link ServiceState#FAILED} state.
+   *
+   * Call this whenever the service considers itself to have failed in a
+   * non-restartable manner.
+   *
+   * If the service was already terminated or failed, this operation does
+   * not trigger a state change.
+   * @param cause the cause of the failure
+   */
+  public final void enterFailedState(Throwable cause) {
+    synchronized (this) {
+      if(failureCause == null) {
+        failureCause = cause;
+      }
+    }
+    if(!isTerminated()) {
+      setServiceState(ServiceState.FAILED);
+    }
+  }
+
+
+  /**
+   * Shortcut method to enter the {...@link ServiceState#LIVE} state.
+   *
+   * Call this when a service considers itself live
+   * @return true if this state was entered, false if it was already in it
+   * @throws ServiceStateException if the service is not currently in the
+   * STARTED or LIVE states
+   */
+  protected final boolean enterLiveState() throws ServiceStateException {
+    return enterState(ServiceState.LIVE);
+  }
+
+  /**
+   * Test for the service being terminated; non-blocking
+   *
+   * @return true if the service is currently terminated
+   */
+  public boolean isTerminated() {
+    return getServiceState() == ServiceState.CLOSED;
+  }
+
+
+  /**
+   * Override point: the name of this service. This is used
+   * to construct human-readable descriptions
+   * @return the name of this service for string messages
+   */
+  public String getServiceName() {
+    return "Service";
+  }
+
+  /**
+  * The toString operator returns the super class name/id, and the state. This
+  * gives all services a slightly useful message in a debugger or test report
+  *
+  * @return a string representation of the object.
+  */
+  @Override
+  public String toString() {
+    return getServiceName() + " instance " + super.toString() + " in state "
+            + getServiceState();
+  }
+
+
+  /**
+   * Get the cause of failure -will be null if not failed, and may be
+   * null even after failure.
+   * @return the exception that triggered entry into the failed state.
+   *
+   */
+  public Throwable getFailureCause() {
+    return failureCause;
+  }
+
+  /**
+   * Initialize and start a service. If the service fails to come up, it is
+   * terminated.
+   *
+   * @param service the service to deploy
+   * @throws IOException on any failure to deploy
+   */
+  public static void startService(Service service)
+          throws IOException {
+    try {
+      service.start();
+    } catch (IOException e) {
+      //mark as failed
+      service.enterFailedState(e);
+      //we assume that the service really does know how to terminate
+      service.closeQuietly();
+      throw e;
+    } catch (Throwable t) {
+        //mark as failed
+        service.enterFailedState(t);
+        //we assume that the service really does know how to terminate
+        service.closeQuietly();
+        //and wrap the exception in an IOException that is rethrown
+        throw (IOException) new IOException(t.toString()).initCause(t);
+    }
+  }
+
+  /**
+   * Terminate a service that is not null
+   *
+   * @param service a service to terminate
+   */
+  public static void closeQuietly(Service service) {
+    if (service != null) {
+      service.closeQuietly();
+    }
+  }
+
+  /**
+   * Terminate a service or other closeable that is not null
+   *
+   * @param closeable the object to close
+   * @throws IOException any exception during the close operation
+   */
+  public static void close(Closeable closeable) {
+    if (closeable != null) {
+      try {
+        closeable.close();
+      } catch (IOException e) {
+        LOG.info("when closing :" + closeable+ ":" + e, e);
+      }
+    }
+  }
+
+
+  /**
+   * An exception that indicates there is something wrong with the state of the
+   * service
+   */
+  public static class ServiceStateException extends IOException {
+    private ServiceState state;
+
+
+    /**
+     * Create a service state exception with a standard message {...@link
+     * Service#ERROR_WRONG_STATE} including the string value of the owning
+     * service, and the supplied state value
+     *
+     * @param service owning service
+     * @param state current state
+     */
+    public ServiceStateException(Service service, ServiceState state) {
+      this(service.toString()
+              + ERROR_WRONG_STATE + " : " + state,
+              null,
+              state);
+    }
+
+    /**
+     * Constructs an Exception with the specified detail message and service
+     * state.
+     *
+     * @param message The detail message (which is saved for later retrieval by
+     *                the {...@link #getMessage()} method)
+     * @param state   the current state of the service
+     */
+    public ServiceStateException(String message, ServiceState state) {
+      this(message, null, state);
+    }
+
+    /**
+     * Constructs an Exception with the specified detail message, cause and
+     * service state.
+     *
+     * @param message message
+     * @param cause   optional root cause
+     * @param state   the state of the component
+     */
+    public ServiceStateException(String message,
+                                 Throwable cause,
+                                 ServiceState state) {
+      super(message, cause);
+      this.state = state;
+    }
+
+    /**
+     * Construct an exception. The lifecycle state of the specific component is
+     * extracted
+     *
+     * @param message message
+     * @param cause   optional root cause
+     * @param service originating service
+     */
+    public ServiceStateException(String message,
+                                 Throwable cause,
+                                 Service service) {
+      this(message, cause, service.getServiceState());
+    }
+
+    /**
+     * Get the state when this exception was raised
+     *
+     * @return the state of the service
+     */
+    public ServiceState getState() {
+      return state;
+    }
+
+
+  }
+
+  /**
+   * This is an exception that can be raised on a liveness failure.
+   */
+  public static class LivenessException extends IOException {
+
+    /**
+     * Constructs an exception with {...@code null} as its error detail 
message.
+     */
+    public LivenessException() {
+    }
+
+    /**
+     * Constructs an Exception with the specified detail message.
+     *
+     * @param message The detail message (which is saved for later retrieval by
+     *                the {...@link #getMessage()} method)
+     */
+    public LivenessException(String message) {
+      super(message);
+    }
+
+    /**
+     * Constructs an exception with the specified detail message and cause.
+     *
+     * <p> The detail message associated with {...@code cause} is only 
incorporated
+     * into this exception's detail message when the message parameter is null.
+     *
+     * @param message The detail message (which is saved for later retrieval by
+     *                the {...@link #getMessage()} method)
+     * @param cause   The cause (which is saved for later retrieval by the
+     *                {...@link #getCause()} method).  (A null value is 
permitted,
+     *                and indicates that the cause is nonexistent or unknown.)
+     */
+    public LivenessException(String message, Throwable cause) {
+      super(message, cause);
+    }
+
+    /**
+     * Constructs an exception with the specified cause and a detail message of
+     * {...@code cause.toString())}. A null cause is allowed.
+     *
+     * @param cause The cause (which is saved for later retrieval by the 
{...@link
+     *              #getCause()} method). Can be null.
+     */
+    public LivenessException(Throwable cause) {
+      super(cause);
+    }
+  }
+
+  /**
+   * The state of the service as perceived by the service itself. Failure is 
the
+   * odd one as it often takes a side effecting test (or an outsider) to
+   * observe.
+   */
+  public enum ServiceState {
+    /**
+     * we don't know or care what state the service is in
+     */
+    UNDEFINED,
+    /**
+     * The service has been created
+     */
+    CREATED,
+
+    /**
+     * The service is starting up.
+     * Its {...@link Service#start()} method has been called.
+     * When it is ready for work, it will declare itself LIVE.
+     */
+    STARTED,
+    /**
+     * The service is now live and available for external use
+     */
+    LIVE,
+    /**
+     * The service has failed
+     */
+    FAILED,
+      /**
+     * the service has been shut down
+     * The container process may now destroy the instance
+     * Its {...@link Service#close()} ()} method has been called.
+     */
+    CLOSED
+  }
+
+  /**
+   * This is the full service status
+   */
+  public static final class ServiceStatus implements Serializable {
+    /** enumerated state */
+    private ServiceState state;
+
+    /** name of the service */
+    private String name;
+
+    /** when did the state change?  */
+    private Date lastStateChange;
+
+    /**
+     * a possibly null array of exceptions that caused a system failure
+     */
+    public ArrayList<Throwable> throwables = new ArrayList<Throwable>(0);
+
+    /**
+     * Create an empty service status instance
+     */
+    public ServiceStatus() {
+    }
+
+    /**
+     * Create a service status instance with the base values set
+     * @param name service name
+     * @param state current state
+     * @param lastStateChange when did the state last change?
+     */
+    public ServiceStatus(String name, ServiceState state,
+                         Date lastStateChange) {
+      this.state = state;
+      this.name = name;
+      this.lastStateChange = lastStateChange;
+    }
+
+    /**
+     * Create a service status instance from the given service
+     *
+     * @param service service to read from
+     */
+    public ServiceStatus(Service service) {
+      name = service.getServiceName();
+      updateState(service);
+    }
+
+    /**
+     * Add a new throwable to the list. This is a no-op if it is null.
+     * To be safely sent over a network connection, the Throwable (and any
+     * chained causes) must be fully serializable.
+     * @param thrown the throwable. Can be null; will not be cloned.
+     */
+    public void addThrowable(Throwable thrown) {
+      if (thrown != null) {
+        throwables.add(thrown);
+      }
+    }
+
+    public List<Throwable> getThrowables() {
+      return throwables;
+    }
+
+    /**
+     * Get the current state
+     * @return the state
+     */
+    public ServiceState getState() {
+      return state;
+    }
+
+    /**
+     * set the state
+     * @param state new state
+     */
+    public void setState(ServiceState state) {
+      this.state = state;
+    }
+
+    /**
+     * Get the name of the service
+     * @return the service name
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * Set the name of the service
+     * @param name the service name
+     */
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    /**
+     * Get the timestamp of the last state change
+     * @return when the service state last changed
+     */
+    public Date getLastStateChange() {
+      return lastStateChange;
+    }
+
+    /**
+     * Set the last state change
+     * @param lastStateChange the timestamp of the last state change
+     */
+    public void setLastStateChange(Date lastStateChange) {
+      this.lastStateChange = lastStateChange;
+    }
+
+    /**
+     * Update the service state
+     * @param service the service to update from
+     */
+    public void updateState(Service service) {
+      synchronized (service) {
+        setState(service.getServiceState());
+        setLastStateChange(service.lastStateChange);
+      }
+    }
+
+    /**
+     * The string operator includes the messages of every throwable
+     * in the list of failures
+     * @return the list of throwables
+     */
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append(getName()).append(" in state ").append(getState());
+      for (Throwable t : throwables) {
+        builder.append("\n ").append(t.toString());
+      }
+      return builder.toString();
+    }
+  }
+}


Reply via email to