/*
 * 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.hbase.master;

import com.google.protobuf.Service;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.ClusterMetrics;
import org.apache.hadoop.hbase.MetaMutationAnnotation;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.SharedConnection;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.MasterSwitchType;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.SnapshotDescription;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.coprocessor.BaseEnvironment;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
import org.apache.hadoop.hbase.coprocessor.CoprocessorServiceBackwardCompatiblity;
import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
import org.apache.hadoop.hbase.coprocessor.HasMasterServices;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.MasterObserver;
import org.apache.hadoop.hbase.coprocessor.MetricsCoprocessor;
import org.apache.hadoop.hbase.master.locking.LockProcedure;
import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
import org.apache.hadoop.hbase.metrics.MetricRegistry;
import org.apache.hadoop.hbase.net.Address;
import org.apache.hadoop.hbase.procedure2.LockType;
import org.apache.hadoop.hbase.procedure2.LockedResource;
import org.apache.hadoop.hbase.procedure2.Procedure;
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
import org.apache.hadoop.hbase.quotas.GlobalQuotaSettings;
import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.hadoop.hbase.security.access.UserPermission;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides the coprocessor framework and environment for master oriented
 * operations.  {@link HMaster} interacts with the loaded coprocessors
 * through this class.
 */
@InterfaceAudience.Private
public class MasterCoprocessorHost
    extends CoprocessorHost<MasterCoprocessor, MasterCoprocessorEnvironment> {

  private static final Logger LOG = LoggerFactory.getLogger(MasterCoprocessorHost.class);

  /**
   * Coprocessor environment extension providing access to master related
   * services.
   */
  private static class MasterEnvironment extends BaseEnvironment<MasterCoprocessor>
      implements MasterCoprocessorEnvironment {
    private final MetricRegistry metricRegistry;
    private final MasterServices services;

    public MasterEnvironment(final MasterCoprocessor impl, final int priority, final int seq,
        final Configuration conf, final MasterServices services) {
      super(impl, priority, seq, conf);
      this.services = services;
      this.metricRegistry =
          MetricsCoprocessor.createRegistryForMasterCoprocessor(impl.getClass().getName());
    }

    @Override
    public ServerName getServerName() {
      return this.services.getServerName();
    }

    @Override
    public Connection getConnection() {
      return new SharedConnection(this.services.getConnection());
    }

    @Override
    public Connection createConnection(Configuration conf) throws IOException {
      return this.services.createConnection(conf);
    }

    @Override
    public MetricRegistry getMetricRegistryForMaster() {
      return metricRegistry;
    }

    @Override
    public void shutdown() {
      super.shutdown();
      MetricsCoprocessor.removeRegistry(this.metricRegistry);
    }
  }

  /**
   * Special version of MasterEnvironment that exposes MasterServices for Core Coprocessors only.
   * Temporary hack until Core Coprocessors are integrated into Core.
   */
  private static class MasterEnvironmentForCoreCoprocessors extends MasterEnvironment
      implements HasMasterServices {
    private final MasterServices masterServices;

    public MasterEnvironmentForCoreCoprocessors(final MasterCoprocessor impl, final int priority,
        final int seq, final Configuration conf, final MasterServices services) {
      super(impl, priority, seq, conf, services);
      this.masterServices = services;
    }

    /**
     * @return An instance of MasterServices, an object NOT for general user-space Coprocessor
     * consumption.
     */
    @Override
    public MasterServices getMasterServices() {
      return this.masterServices;
    }
  }

  private MasterServices masterServices;

  public MasterCoprocessorHost(final MasterServices services, final Configuration conf) {
    super(services);
    this.conf = conf;
    this.masterServices = services;
    // Log the state of coprocessor loading here; should appear only once or
    // twice in the daemon log, depending on HBase version, because there is
    // only one MasterCoprocessorHost instance in the master process
    boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY,
      DEFAULT_COPROCESSORS_ENABLED);
    LOG.trace("System coprocessor loading is {}",  (coprocessorsEnabled ? "enabled" : "disabled"));
    loadSystemCoprocessors(conf, MASTER_COPROCESSOR_CONF_KEY);
  }

  @Override
  public MasterEnvironment createEnvironment(final MasterCoprocessor instance, final int priority,
      final int seq, final Configuration conf) {
    // If coprocessor exposes any services, register them.
    for (Service service : instance.getServices()) {
      masterServices.registerService(service);
    }
    // If a CoreCoprocessor, return a 'richer' environment, one laden with MasterServices.
    return instance.getClass().isAnnotationPresent(CoreCoprocessor.class)?
        new MasterEnvironmentForCoreCoprocessors(instance, priority, seq, conf, masterServices):
        new MasterEnvironment(instance, priority, seq, conf, masterServices);
  }

  @Override
  public MasterCoprocessor checkAndGetInstance(Class<?> implClass)
      throws InstantiationException, IllegalAccessException {
    try {
      if (MasterCoprocessor.class.isAssignableFrom(implClass)) {
        return implClass.asSubclass(MasterCoprocessor.class).getDeclaredConstructor().newInstance();
      } else if (CoprocessorService.class.isAssignableFrom(implClass)) {
        // For backward compatibility with old CoprocessorService impl which don't extend
        // MasterCoprocessor.
        CoprocessorService cs;
        cs = implClass.asSubclass(CoprocessorService.class).getDeclaredConstructor().newInstance();
        return new CoprocessorServiceBackwardCompatiblity.MasterCoprocessorService(cs);
      } else {
        LOG.error("{} is not of type MasterCoprocessor. Check the configuration of {}",
            implClass.getName(), CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
        return null;
      }
    } catch (NoSuchMethodException | InvocationTargetException e) {
      throw (InstantiationException) new InstantiationException(implClass.getName()).initCause(e);
    }
  }

  private ObserverGetter<MasterCoprocessor, MasterObserver> masterObserverGetter =
      MasterCoprocessor::getMasterObserver;

  abstract class MasterObserverOperation extends
      ObserverOperationWithoutResult<MasterObserver> {
    public MasterObserverOperation(){
      super(masterObserverGetter);
    }

    public MasterObserverOperation(boolean bypassable) {
      this(null, bypassable);
    }

    public MasterObserverOperation(User user) {
      super(masterObserverGetter, user);
    }

    public MasterObserverOperation(User user, boolean bypassable) {
      super(masterObserverGetter, user, bypassable);
    }
  }


  //////////////////////////////////////////////////////////////////////////////////////////////////
  // MasterObserver operations
  //////////////////////////////////////////////////////////////////////////////////////////////////


  public void preCreateNamespace(final NamespaceDescriptor ns) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preCreateNamespace(this, ns);
      }
    });
  }

  public void postCreateNamespace(final NamespaceDescriptor ns) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCreateNamespace(this, ns);
      }
    });
  }

  public void preDeleteNamespace(final String namespaceName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDeleteNamespace(this, namespaceName);
      }
    });
  }

  public void postDeleteNamespace(final String namespaceName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postDeleteNamespace(this, namespaceName);
      }
    });
  }

  public void preModifyNamespace(final NamespaceDescriptor currentNsDescriptor,
    final NamespaceDescriptor newNsDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preModifyNamespace(this, currentNsDescriptor, newNsDescriptor);
      }
    });
  }

  public void postModifyNamespace(final NamespaceDescriptor oldNsDescriptor,
    final NamespaceDescriptor currentNsDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postModifyNamespace(this, oldNsDescriptor, currentNsDescriptor);
      }
    });
  }

  public void preGetNamespaceDescriptor(final String namespaceName)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetNamespaceDescriptor(this, namespaceName);
      }
    });
  }

  public void postGetNamespaceDescriptor(final NamespaceDescriptor ns)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetNamespaceDescriptor(this, ns);
      }
    });
  }

  public void preListNamespaceDescriptors(final List<NamespaceDescriptor> descriptors)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preListNamespaceDescriptors(this, descriptors);
      }
    });
  }

  public void postListNamespaceDescriptors(final List<NamespaceDescriptor> descriptors)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postListNamespaceDescriptors(this, descriptors);
      }
    });
  }

  /* Implementation of hooks for invoking MasterObservers */

  public TableDescriptor preCreateTableRegionsInfos(TableDescriptor desc) throws IOException {
    if (coprocEnvironments.isEmpty()) {
      return desc;
    }
    return execOperationWithResult(
      new ObserverOperationWithResult<MasterObserver, TableDescriptor>(masterObserverGetter, desc) {

        @Override
        protected TableDescriptor call(MasterObserver observer) throws IOException {
          return observer.preCreateTableRegionsInfos(this, getResult());
        }
      });
  }

  public void preCreateTable(final TableDescriptor htd, final RegionInfo[] regions)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preCreateTable(this, htd, regions);
      }
    });
  }

  public void postCreateTable(final TableDescriptor htd, final RegionInfo[] regions)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCreateTable(this, htd, regions);
      }
    });
  }

  public void preCreateTableAction(final TableDescriptor htd, final RegionInfo[] regions,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preCreateTableAction(this, htd, regions);
      }
    });
  }

  public void postCompletedCreateTableAction(
      final TableDescriptor htd, final RegionInfo[] regions, final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedCreateTableAction(this, htd, regions);
      }
    });
  }

  public void preDeleteTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDeleteTable(this, tableName);
      }
    });
  }

  public void postDeleteTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postDeleteTable(this, tableName);
      }
    });
  }

  public void preDeleteTableAction(final TableName tableName, final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDeleteTableAction(this, tableName);
      }
    });
  }

  public void postCompletedDeleteTableAction(final TableName tableName, final User user)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedDeleteTableAction(this, tableName);
      }
    });
  }

  public void preTruncateTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preTruncateTable(this, tableName);
      }
    });
  }

  public void postTruncateTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postTruncateTable(this, tableName);
      }
    });
  }

  public void preTruncateTableAction(final TableName tableName, final User user)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preTruncateTableAction(this, tableName);
      }
    });
  }

  public void postCompletedTruncateTableAction(final TableName tableName, final User user)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedTruncateTableAction(this, tableName);
      }
    });
  }

  public TableDescriptor preModifyTable(final TableName tableName,
      final TableDescriptor currentDescriptor, final TableDescriptor newDescriptor)
      throws IOException {
    if (coprocEnvironments.isEmpty()) {
      return newDescriptor;
    }
    return execOperationWithResult(new ObserverOperationWithResult<MasterObserver, TableDescriptor>(
        masterObserverGetter, newDescriptor) {
      @Override
      protected TableDescriptor call(MasterObserver observer) throws IOException {
        return observer.preModifyTable(this, tableName, currentDescriptor, getResult());
      }
    });
  }

  public void postModifyTable(final TableName tableName, final TableDescriptor oldDescriptor,
    final TableDescriptor currentDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postModifyTable(this, tableName, oldDescriptor, currentDescriptor);
      }
    });
  }

  public void preModifyTableAction(final TableName tableName,
    final TableDescriptor currentDescriptor, final TableDescriptor newDescriptor, final User user)
    throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preModifyTableAction(this, tableName, currentDescriptor, newDescriptor);
      }
    });
  }

  public void postCompletedModifyTableAction(final TableName tableName,
    final TableDescriptor oldDescriptor, final TableDescriptor currentDescriptor, final User user)
    throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedModifyTableAction(this, tableName, oldDescriptor, currentDescriptor);
      }
    });
  }

  public void preEnableTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preEnableTable(this, tableName);
      }
    });
  }

  public void postEnableTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postEnableTable(this, tableName);
      }
    });
  }

  public void preEnableTableAction(final TableName tableName, final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preEnableTableAction(this, tableName);
      }
    });
  }

  public void postCompletedEnableTableAction(final TableName tableName, final User user)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedEnableTableAction(this, tableName);
      }
    });
  }

  public void preDisableTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDisableTable(this, tableName);
      }
    });
  }

  public void postDisableTable(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postDisableTable(this, tableName);
      }
    });
  }

  public void preDisableTableAction(final TableName tableName, final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDisableTableAction(this, tableName);
      }
    });
  }

  public void postCompletedDisableTableAction(final TableName tableName, final User user)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedDisableTableAction(this, tableName);
      }
    });
  }

  public void preAbortProcedure(
      final ProcedureExecutor<MasterProcedureEnv> procEnv,
      final long procId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preAbortProcedure(this,  procId);
      }
    });
  }

  public void postAbortProcedure() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postAbortProcedure(this);
      }
    });
  }

  public void preGetProcedures() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetProcedures(this);
      }
    });
  }

  public void postGetProcedures(final List<Procedure<?>> procInfoList) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetProcedures(this);
      }
    });
  }

  public void preGetLocks() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetLocks(this);
      }
    });
  }

  public void postGetLocks(final List<LockedResource> lockedResources) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetLocks(this);
      }
    });
  }

  public void preMove(final RegionInfo region, final ServerName srcServer,
      final ServerName destServer) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMove(this, region, srcServer, destServer);
      }
    });
  }

  public void postMove(final RegionInfo region, final ServerName srcServer,
      final ServerName destServer) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postMove(this, region, srcServer, destServer);
      }
    });
  }

  public void preAssign(final RegionInfo regionInfo) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preAssign(this, regionInfo);
      }
    });
  }

  public void postAssign(final RegionInfo regionInfo) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postAssign(this, regionInfo);
      }
    });
  }

  public void preUnassign(final RegionInfo regionInfo, final boolean force)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preUnassign(this, regionInfo, force);
      }
    });
  }

  public void postUnassign(final RegionInfo regionInfo, final boolean force) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postUnassign(this, regionInfo, force);
      }
    });
  }

  public void preRegionOffline(final RegionInfo regionInfo) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRegionOffline(this, regionInfo);
      }
    });
  }

  public void postRegionOffline(final RegionInfo regionInfo) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRegionOffline(this, regionInfo);
      }
    });
  }

  public void preMergeRegions(final RegionInfo[] regionsToMerge)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMergeRegions(this, regionsToMerge);
      }
    });
  }

  public void postMergeRegions(final RegionInfo[] regionsToMerge)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postMergeRegions(this, regionsToMerge);
      }
    });
  }

  public boolean preBalance() throws IOException {
    return execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preBalance(this);
      }
    });
  }

  public void postBalance(final List<RegionPlan> plans) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postBalance(this, plans);
      }
    });
  }

  public void preSetSplitOrMergeEnabled(final boolean newValue,
      final MasterSwitchType switchType) throws IOException {
    execOperation(coprocEnvironments.isEmpty()? null: new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetSplitOrMergeEnabled(this, newValue, switchType);
      }
    });
  }

  public void postSetSplitOrMergeEnabled(final boolean newValue,
      final MasterSwitchType switchType) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetSplitOrMergeEnabled(this, newValue, switchType);
      }
    });
  }

  /**
   * Invoked just before calling the split region procedure
   * @param tableName the table where the region belongs to
   * @param splitRow the split point
   * @throws IOException
   */
  public void preSplitRegion(
      final TableName tableName,
      final byte[] splitRow) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSplitRegion(this, tableName, splitRow);
      }
    });
  }

  /**
   * Invoked just before a split
   * @param tableName the table where the region belongs to
   * @param splitRow the split point
   * @param user the user
   * @throws IOException
   */
  public void preSplitRegionAction(
      final TableName tableName,
      final byte[] splitRow,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSplitRegionAction(this, tableName, splitRow);
      }
    });
  }

  /**
   * Invoked just after a split
   * @param regionInfoA the new left-hand daughter region
   * @param regionInfoB the new right-hand daughter region
   * @param user the user
   * @throws IOException
   */
  public void postCompletedSplitRegionAction(
      final RegionInfo regionInfoA,
      final RegionInfo regionInfoB,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedSplitRegionAction(this, regionInfoA, regionInfoB);
      }
    });
  }

  /**
   * This will be called before update META step as part of split table region procedure.
   * @param splitKey
   * @param metaEntries
   * @param user the user
   * @throws IOException
   */
  public void preSplitBeforeMETAAction(
      final byte[] splitKey,
      final List<Mutation> metaEntries,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSplitRegionBeforeMETAAction(this, splitKey, metaEntries);
      }
    });
  }

  /**
   * This will be called after update META step as part of split table region procedure.
   * @param user the user
   * @throws IOException
   */
  public void preSplitAfterMETAAction(final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSplitRegionAfterMETAAction(this);
      }
    });
  }

  /**
   * Invoked just after the rollback of a failed split
   * @param user the user
   * @throws IOException
   */
  public void postRollBackSplitRegionAction(final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRollBackSplitRegionAction(this);
      }
    });
  }

  /**
   * Invoked just before a merge
   * @param regionsToMerge the regions to merge
   * @param user the user
   * @throws IOException
   */
  public void preMergeRegionsAction(
      final RegionInfo[] regionsToMerge, final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMergeRegionsAction(this, regionsToMerge);
      }
    });
  }

  /**
   * Invoked after completing merge regions operation
   * @param regionsToMerge the regions to merge
   * @param mergedRegion the new merged region
   * @param user the user
   * @throws IOException
   */
  public void postCompletedMergeRegionsAction(
      final RegionInfo[] regionsToMerge,
      final RegionInfo mergedRegion,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCompletedMergeRegionsAction(this, regionsToMerge, mergedRegion);
      }
    });
  }

  /**
   * Invoked before merge regions operation writes the new region to hbase:meta
   * @param regionsToMerge the regions to merge
   * @param metaEntries the meta entry
   * @param user the user
   * @throws IOException
   */
  public void preMergeRegionsCommit(
      final RegionInfo[] regionsToMerge,
      final @MetaMutationAnnotation List<Mutation> metaEntries,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMergeRegionsCommitAction(this, regionsToMerge, metaEntries);
      }
    });
  }

  /**
   * Invoked after merge regions operation writes the new region to hbase:meta
   * @param regionsToMerge the regions to merge
   * @param mergedRegion the new merged region
   * @param user the user
   * @throws IOException
   */
  public void postMergeRegionsCommit(
      final RegionInfo[] regionsToMerge,
      final RegionInfo mergedRegion,
      final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postMergeRegionsCommitAction(this, regionsToMerge, mergedRegion);
      }
    });
  }

  /**
   * Invoked after rollback merge regions operation
   * @param regionsToMerge the regions to merge
   * @param user the user
   * @throws IOException
   */
  public void postRollBackMergeRegionsAction(
      final RegionInfo[] regionsToMerge, final User user) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRollBackMergeRegionsAction(this, regionsToMerge);
      }
    });
  }

  // This hook allows Coprocessor change value of balance switch.
  public void preBalanceSwitch(final boolean b) throws IOException {
    if (this.coprocEnvironments.isEmpty()) {
      return;
    }
    execOperation(new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preBalanceSwitch(this, b);
      }
    });
  }

  public void postBalanceSwitch(final boolean oldValue, final boolean newValue)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postBalanceSwitch(this, oldValue, newValue);
      }
    });
  }

  public void preShutdown() throws IOException {
    // While stopping the cluster all coprocessors method should be executed first then the
    // coprocessor should be cleaned up.
    if (coprocEnvironments.isEmpty()) {
      return;
    }
    execShutdown(new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preShutdown(this);
      }
      @Override
      public void postEnvCall() {
        // invoke coprocessor stop method
        shutdown(this.getEnvironment());
      }
    });
  }

  public void preStopMaster() throws IOException {
    // While stopping master all coprocessors method should be executed first then the coprocessor
    // environment should be cleaned up.
    if (coprocEnvironments.isEmpty()) {
      return;
    }
    execShutdown(new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preStopMaster(this);
      }
      @Override
      public void postEnvCall() {
        // invoke coprocessor stop method
        shutdown(this.getEnvironment());
      }
    });
  }

  public void preMasterInitialization() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMasterInitialization(this);
      }
    });
  }

  public void postStartMaster() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postStartMaster(this);
      }
    });
  }

  public void preSnapshot(final SnapshotDescription snapshot,
      final TableDescriptor hTableDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSnapshot(this, snapshot, hTableDescriptor);
      }
    });
  }

  public void postSnapshot(final SnapshotDescription snapshot,
      final TableDescriptor hTableDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSnapshot(this, snapshot, hTableDescriptor);
      }
    });
  }

  public void preListSnapshot(final SnapshotDescription snapshot) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preListSnapshot(this, snapshot);
      }
    });
  }

  public void postListSnapshot(final SnapshotDescription snapshot) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postListSnapshot(this, snapshot);
      }
    });
  }

  public void preCloneSnapshot(final SnapshotDescription snapshot,
      final TableDescriptor hTableDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preCloneSnapshot(this, snapshot, hTableDescriptor);
      }
    });
  }

  public void postCloneSnapshot(final SnapshotDescription snapshot,
      final TableDescriptor hTableDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postCloneSnapshot(this, snapshot, hTableDescriptor);
      }
    });
  }

  public void preRestoreSnapshot(final SnapshotDescription snapshot,
      final TableDescriptor hTableDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRestoreSnapshot(this, snapshot, hTableDescriptor);
      }
    });
  }

  public void postRestoreSnapshot(final SnapshotDescription snapshot,
      final TableDescriptor hTableDescriptor) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRestoreSnapshot(this, snapshot, hTableDescriptor);
      }
    });
  }

  public void preDeleteSnapshot(final SnapshotDescription snapshot) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDeleteSnapshot(this, snapshot);
      }
    });
  }

  public void postDeleteSnapshot(final SnapshotDescription snapshot) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postDeleteSnapshot(this, snapshot);
      }
    });
  }

  public void preGetTableDescriptors(final List<TableName> tableNamesList,
      final List<TableDescriptor> descriptors, final String regex) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetTableDescriptors(this, tableNamesList, descriptors, regex);
      }
    });
  }

  public void postGetTableDescriptors(final List<TableName> tableNamesList,
      final List<TableDescriptor> descriptors, final String regex) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetTableDescriptors(this, tableNamesList, descriptors, regex);
      }
    });
  }

  public void preGetTableNames(final List<TableDescriptor> descriptors,
      final String regex) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetTableNames(this, descriptors, regex);
      }
    });
  }

  public void postGetTableNames(final List<TableDescriptor> descriptors,
      final String regex) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetTableNames(this, descriptors, regex);
      }
    });
  }

  public void preTableFlush(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preTableFlush(this, tableName);
      }
    });
  }

  public void postTableFlush(final TableName tableName) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postTableFlush(this, tableName);
      }
    });
  }

  public void preSetUserQuota(
      final String user, final GlobalQuotaSettings quotas) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetUserQuota(this, user, quotas);
      }
    });
  }

  public void postSetUserQuota(
      final String user, final GlobalQuotaSettings quotas) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetUserQuota(this, user, quotas);
      }
    });
  }

  public void preSetUserQuota(
      final String user, final TableName table, final GlobalQuotaSettings quotas)
          throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetUserQuota(this, user, table, quotas);
      }
    });
  }

  public void postSetUserQuota(
      final String user, final TableName table, final GlobalQuotaSettings quotas)
          throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetUserQuota(this, user, table, quotas);
      }
    });
  }

  public void preSetUserQuota(
      final String user, final String namespace, final GlobalQuotaSettings quotas)
          throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetUserQuota(this, user, namespace, quotas);
      }
    });
  }

  public void postSetUserQuota(
      final String user, final String namespace, final GlobalQuotaSettings quotas)
          throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetUserQuota(this, user, namespace, quotas);
      }
    });
  }

  public void preSetTableQuota(
      final TableName table, final GlobalQuotaSettings quotas) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetTableQuota(this, table, quotas);
      }
    });
  }

  public void postSetTableQuota(
      final TableName table, final GlobalQuotaSettings quotas) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetTableQuota(this, table, quotas);
      }
    });
  }

  public void preSetNamespaceQuota(
      final String namespace, final GlobalQuotaSettings quotas) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetNamespaceQuota(this, namespace, quotas);
      }
    });
  }

  public void postSetNamespaceQuota(
      final String namespace, final GlobalQuotaSettings quotas) throws IOException{
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetNamespaceQuota(this, namespace, quotas);
      }
    });
  }

  public void preSetRegionServerQuota(final String regionServer, final GlobalQuotaSettings quotas)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSetRegionServerQuota(this, regionServer, quotas);
      }
    });
  }

  public void postSetRegionServerQuota(final String regionServer, final GlobalQuotaSettings quotas)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSetRegionServerQuota(this, regionServer, quotas);
      }
    });
  }

  public void preMoveServersAndTables(final Set<Address> servers, final Set<TableName> tables,
      final String targetGroup) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMoveServersAndTables(this, servers, tables, targetGroup);
      }
    });
  }

  public void postMoveServersAndTables(final Set<Address> servers, final Set<TableName> tables,
      final String targetGroup) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postMoveServersAndTables(this, servers, tables, targetGroup);
      }
    });
  }

  public void preMoveServers(final Set<Address> servers, final String targetGroup)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMoveServers(this, servers, targetGroup);
      }
    });
  }

  public void postMoveServers(final Set<Address> servers, final String targetGroup)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postMoveServers(this, servers, targetGroup);
      }
    });
  }

  public void preMoveTables(final Set<TableName> tables, final String targetGroup)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preMoveTables(this, tables, targetGroup);
      }
    });
  }

  public void postMoveTables(final Set<TableName> tables, final String targetGroup)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postMoveTables(this, tables, targetGroup);
      }
    });
  }

  public void preAddRSGroup(final String name)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preAddRSGroup(this, name);
      }
    });
  }

  public void postAddRSGroup(final String name)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postAddRSGroup(this, name);
      }
    });
  }

  public void preRemoveRSGroup(final String name)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRemoveRSGroup(this, name);
      }
    });
  }

  public void postRemoveRSGroup(final String name)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRemoveRSGroup(this, name);
      }
    });
  }

  public void preBalanceRSGroup(final String name)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preBalanceRSGroup(this, name);
      }
    });
  }

  public void postBalanceRSGroup(final String name, final boolean balanceRan)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postBalanceRSGroup(this, name, balanceRan);
      }
    });
  }

  public void preRenameRSGroup(final String oldName, final String newName)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null: new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRenameRSGroup(this, oldName, newName);
      }
    });
  }

  public void postRenameRSGroup(final String oldName, final String newName)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null: new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRenameRSGroup(this, oldName, newName);
      }
    });
  }

  public void preRemoveServers(final Set<Address> servers)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRemoveServers(this, servers);
      }
    });
  }

  public void postRemoveServers(final Set<Address> servers)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRemoveServers(this, servers);
      }
    });
  }

  public void preAddReplicationPeer(final String peerId, final ReplicationPeerConfig peerConfig)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preAddReplicationPeer(this, peerId, peerConfig);
      }
    });
  }

  public void postAddReplicationPeer(final String peerId, final ReplicationPeerConfig peerConfig)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postAddReplicationPeer(this, peerId, peerConfig);
      }
    });
  }

  public void preRemoveReplicationPeer(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRemoveReplicationPeer(this, peerId);
      }
    });
  }

  public void postRemoveReplicationPeer(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRemoveReplicationPeer(this, peerId);
      }
    });
  }

  public void preEnableReplicationPeer(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preEnableReplicationPeer(this, peerId);
      }
    });
  }

  public void postEnableReplicationPeer(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postEnableReplicationPeer(this, peerId);
      }
    });
  }

  public void preDisableReplicationPeer(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDisableReplicationPeer(this, peerId);
      }
    });
  }

  public void postDisableReplicationPeer(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postDisableReplicationPeer(this, peerId);
      }
    });
  }

  public void preGetReplicationPeerConfig(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetReplicationPeerConfig(this, peerId);
      }
    });
  }

  public void postGetReplicationPeerConfig(final String peerId) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetReplicationPeerConfig(this, peerId);
      }
    });
  }

  public void preUpdateReplicationPeerConfig(final String peerId,
      final ReplicationPeerConfig peerConfig) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preUpdateReplicationPeerConfig(this, peerId, peerConfig);
      }
    });
  }

  public void postUpdateReplicationPeerConfig(final String peerId,
      final ReplicationPeerConfig peerConfig) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postUpdateReplicationPeerConfig(this, peerId, peerConfig);
      }
    });
  }

  public void preListReplicationPeers(final String regex) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preListReplicationPeers(this, regex);
      }
    });
  }

  public void postListReplicationPeers(final String regex) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postListReplicationPeers(this, regex);
      }
    });
  }

  public void preRequestLock(String namespace, TableName tableName, RegionInfo[] regionInfos,
      LockType type, String description) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRequestLock(this, namespace, tableName, regionInfos, description);
      }
    });
  }

  public void postRequestLock(String namespace, TableName tableName, RegionInfo[] regionInfos,
      LockType type, String description) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRequestLock(this, namespace, tableName, regionInfos, description);
      }
    });
  }

  public void preLockHeartbeat(LockProcedure proc, boolean keepAlive) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preLockHeartbeat(this, proc.getTableName(), proc.getDescription());
      }
    });
  }

  public void postLockHeartbeat(LockProcedure proc, boolean keepAlive) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postLockHeartbeat(this);
      }
    });
  }

  public void preGetClusterMetrics() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetClusterMetrics(this);
      }
    });
  }

  public void postGetClusterMetrics(ClusterMetrics status) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetClusterMetrics(this, status);
      }
    });
  }

  public void preClearDeadServers() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preClearDeadServers(this);
      }
    });
  }

  public void postClearDeadServers(List<ServerName> servers,
      List<ServerName> notClearedServers) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postClearDeadServers(this, servers, notClearedServers);
      }
    });
  }

  public void preDecommissionRegionServers(List<ServerName> servers, boolean offload) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preDecommissionRegionServers(this, servers, offload);
      }
    });
  }

  public void postDecommissionRegionServers(List<ServerName> servers, boolean offload) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postDecommissionRegionServers(this, servers, offload);
      }
    });
  }

  public void preListDecommissionedRegionServers() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preListDecommissionedRegionServers(this);
      }
    });
  }

  public void postListDecommissionedRegionServers() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postListDecommissionedRegionServers(this);
      }
    });
  }

  public void preRecommissionRegionServer(ServerName server, List<byte[]> encodedRegionNames)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRecommissionRegionServer(this, server, encodedRegionNames);
      }
    });
  }

  public void postRecommissionRegionServer(ServerName server, List<byte[]> encodedRegionNames)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRecommissionRegionServer(this, server, encodedRegionNames);
      }
    });
  }

  public void preSwitchRpcThrottle(boolean enable) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null :new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSwitchRpcThrottle(this, enable);
      }
    });
  }

  public void postSwitchRpcThrottle(final boolean oldValue, final boolean newValue)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSwitchRpcThrottle(this, oldValue, newValue);
      }
    });
  }

  public void preIsRpcThrottleEnabled() throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preIsRpcThrottleEnabled(this);
      }
    });
  }

  public void postIsRpcThrottleEnabled(boolean enabled) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postIsRpcThrottleEnabled(this, enabled);
      }
    });
  }

  public void preSwitchExceedThrottleQuota(boolean enable) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preSwitchExceedThrottleQuota(this, enable);
      }
    });
  }

  public void postSwitchExceedThrottleQuota(final boolean oldValue, final boolean newValue)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postSwitchExceedThrottleQuota(this, oldValue, newValue);
      }
    });
  }

  public void preGrant(UserPermission userPermission, boolean mergeExistingPermissions)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGrant(this, userPermission, mergeExistingPermissions);
      }
    });
  }

  public void postGrant(UserPermission userPermission, boolean mergeExistingPermissions)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGrant(this, userPermission, mergeExistingPermissions);
      }
    });
  }

  public void preRevoke(UserPermission userPermission) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preRevoke(this, userPermission);
      }
    });
  }

  public void postRevoke(UserPermission userPermission) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postRevoke(this, userPermission);
      }
    });
  }

  public void preGetUserPermissions(String userName, String namespace, TableName tableName,
      byte[] family, byte[] qualifier) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preGetUserPermissions(this, userName, namespace, tableName, family, qualifier);
      }
    });
  }

  public void postGetUserPermissions(String userName, String namespace, TableName tableName,
      byte[] family, byte[] qualifier) throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postGetUserPermissions(this, userName, namespace, tableName, family, qualifier);
      }
    });
  }

  public void preHasUserPermissions(String userName, List<Permission> permissions)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.preHasUserPermissions(this, userName, permissions);
      }
    });
  }

  public void postHasUserPermissions(String userName, List<Permission> permissions)
      throws IOException {
    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
      @Override
      public void call(MasterObserver observer) throws IOException {
        observer.postHasUserPermissions(this, userName, permissions);
      }
    });
  }
}
