001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.coprocessor;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Optional;
027import java.util.Set;
028import java.util.TreeSet;
029import java.util.UUID;
030import java.util.concurrent.ConcurrentSkipListSet;
031import java.util.concurrent.atomic.AtomicInteger;
032import java.util.function.Function;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.fs.Path;
035import org.apache.hadoop.hbase.Abortable;
036import org.apache.hadoop.hbase.Coprocessor;
037import org.apache.hadoop.hbase.CoprocessorEnvironment;
038import org.apache.hadoop.hbase.DoNotRetryIOException;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.ipc.RpcServer;
041import org.apache.hadoop.hbase.security.User;
042import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
043import org.apache.hadoop.hbase.util.SortedList;
044import org.apache.yetus.audience.InterfaceAudience;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import org.apache.hbase.thirdparty.com.google.common.base.Strings;
049
050/**
051 * Provides the common setup framework and runtime services for coprocessor invocation from HBase
052 * services.
053 * @param <C> type of specific coprocessor this host will handle
054 * @param <E> type of specific coprocessor environment this host requires. provides
055 */
056@InterfaceAudience.Private
057public abstract class CoprocessorHost<C extends Coprocessor, E extends CoprocessorEnvironment<C>> {
058  public static final String REGION_COPROCESSOR_CONF_KEY = "hbase.coprocessor.region.classes";
059  public static final String REGIONSERVER_COPROCESSOR_CONF_KEY =
060    "hbase.coprocessor.regionserver.classes";
061  public static final String USER_REGION_COPROCESSOR_CONF_KEY =
062    "hbase.coprocessor.user.region.classes";
063  public static final String MASTER_COPROCESSOR_CONF_KEY = "hbase.coprocessor.master.classes";
064  public static final String WAL_COPROCESSOR_CONF_KEY = "hbase.coprocessor.wal.classes";
065  public static final String RPC_COPROCESSOR_CONF_KEY = "hbase.coprocessor.rpc.classes";
066  public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror";
067  public static final boolean DEFAULT_ABORT_ON_ERROR = true;
068  public static final String COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.enabled";
069  public static final boolean DEFAULT_COPROCESSORS_ENABLED = true;
070  public static final String USER_COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.user.enabled";
071  public static final boolean DEFAULT_USER_COPROCESSORS_ENABLED = true;
072  public static final String SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR =
073    "hbase.skip.load.duplicate.table.coprocessor";
074  public static final boolean DEFAULT_SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR = false;
075
076  private static final Logger LOG = LoggerFactory.getLogger(CoprocessorHost.class);
077  protected Abortable abortable;
078  /** Ordered set of loaded coprocessors with lock */
079  protected final SortedList<E> coprocEnvironments =
080    new SortedList<>(new EnvironmentPriorityComparator());
081  protected Configuration conf;
082  // unique file prefix to use for local copies of jars when classloading
083  protected String pathPrefix;
084  protected AtomicInteger loadSequence = new AtomicInteger();
085
086  public CoprocessorHost(Abortable abortable) {
087    this.abortable = abortable;
088    this.pathPrefix = UUID.randomUUID().toString();
089  }
090
091  /**
092   * Not to be confused with the per-object _coprocessors_ (above), coprocessorNames is static and
093   * stores the set of all coprocessors ever loaded by any thread in this JVM. It is strictly
094   * additive: coprocessors are added to coprocessorNames, by checkAndLoadInstance() but are never
095   * removed, since the intention is to preserve a history of all loaded coprocessors for diagnosis
096   * in case of server crash (HBASE-4014).
097   */
098  private static Set<String> coprocessorNames = Collections.synchronizedSet(new HashSet<String>());
099
100  public static Set<String> getLoadedCoprocessors() {
101    synchronized (coprocessorNames) {
102      return new HashSet(coprocessorNames);
103    }
104  }
105
106  /**
107   * Used to create a parameter to the HServerLoad constructor so that HServerLoad can provide
108   * information about the coprocessors loaded by this regionserver. (HBASE-4070: Improve region
109   * server metrics to report loaded coprocessors to master).
110   */
111  public Set<String> getCoprocessors() {
112    Set<String> returnValue = new TreeSet<>();
113    for (E e : coprocEnvironments) {
114      returnValue.add(e.getInstance().getClass().getSimpleName());
115    }
116    return returnValue;
117  }
118
119  /**
120   * Load system coprocessors once only. Read the class names from configuration. Called by
121   * constructor.
122   */
123  protected void loadSystemCoprocessors(Configuration conf, String confKey) {
124    boolean coprocessorsEnabled =
125      conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY, DEFAULT_COPROCESSORS_ENABLED);
126    if (!coprocessorsEnabled) {
127      return;
128    }
129
130    Class<?> implClass;
131
132    // load default coprocessors from configure file
133    String[] defaultCPClasses = conf.getStrings(confKey);
134    if (defaultCPClasses == null || defaultCPClasses.length == 0) return;
135
136    int currentSystemPriority = Coprocessor.PRIORITY_SYSTEM;
137    for (String className : defaultCPClasses) {
138      // After HBASE-23710 and HBASE-26714 when configuring for system coprocessor, we accept
139      // an optional format of className|priority|path
140      String[] classNameToken = className.split("\\|");
141      boolean hasPriorityOverride = false;
142      boolean hasPath = false;
143      className = classNameToken[0];
144      int overridePriority = Coprocessor.PRIORITY_SYSTEM;
145      Path path = null;
146      if (classNameToken.length > 1 && !Strings.isNullOrEmpty(classNameToken[1])) {
147        overridePriority = Integer.parseInt(classNameToken[1]);
148        hasPriorityOverride = true;
149      }
150      if (classNameToken.length > 2 && !Strings.isNullOrEmpty(classNameToken[2])) {
151        path = new Path(classNameToken[2].trim());
152        hasPath = true;
153      }
154      className = className.trim();
155      if (findCoprocessor(className) != null) {
156        // If already loaded will just continue
157        LOG.warn("Attempted duplicate loading of " + className + "; skipped");
158        continue;
159      }
160      ClassLoader cl = this.getClass().getClassLoader();
161      try {
162        // override the class loader if a path for the system coprocessor is provided.
163        if (hasPath) {
164          cl = CoprocessorClassLoader.getClassLoader(path, this.getClass().getClassLoader(),
165            pathPrefix, conf);
166        }
167        Thread.currentThread().setContextClassLoader(cl);
168        implClass = cl.loadClass(className);
169        int coprocPriority = hasPriorityOverride ? overridePriority : currentSystemPriority;
170        // Add coprocessors as we go to guard against case where a coprocessor is specified twice
171        // in the configuration
172        E env = checkAndLoadInstance(implClass, coprocPriority, conf);
173        if (env != null) {
174          this.coprocEnvironments.add(env);
175          LOG.info("System coprocessor {} loaded, priority={}.", className, coprocPriority);
176          if (!hasPriorityOverride) {
177            ++currentSystemPriority;
178          }
179        }
180      } catch (Throwable t) {
181        // We always abort if system coprocessors cannot be loaded
182        abortServer(className, t);
183      }
184    }
185  }
186
187  /**
188   * Load a coprocessor implementation into the host
189   * @param path      path to implementation jar
190   * @param className the main class name
191   * @param priority  chaining priority
192   * @param conf      configuration for coprocessor
193   * @throws java.io.IOException Exception
194   */
195  public E load(Path path, String className, int priority, Configuration conf) throws IOException {
196    String[] includedClassPrefixes = null;
197    if (conf.get(HConstants.CP_HTD_ATTR_INCLUSION_KEY) != null) {
198      String prefixes = conf.get(HConstants.CP_HTD_ATTR_INCLUSION_KEY);
199      includedClassPrefixes = prefixes.split(";");
200    }
201    return load(path, className, priority, conf, includedClassPrefixes);
202  }
203
204  /**
205   * Load a coprocessor implementation into the host
206   * @param path                  path to implementation jar
207   * @param className             the main class name
208   * @param priority              chaining priority
209   * @param conf                  configuration for coprocessor
210   * @param includedClassPrefixes class name prefixes to include
211   * @throws java.io.IOException Exception
212   */
213  public E load(Path path, String className, int priority, Configuration conf,
214    String[] includedClassPrefixes) throws IOException {
215    Class<?> implClass;
216    LOG.debug("Loading coprocessor class " + className + " with path " + path + " and priority "
217      + priority);
218
219    boolean skipLoadDuplicateCoprocessor = conf.getBoolean(SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR,
220      DEFAULT_SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR);
221    if (skipLoadDuplicateCoprocessor && findCoprocessor(className) != null) {
222      // If already loaded will just continue
223      LOG.warn("Attempted duplicate loading of {}; skipped", className);
224      return null;
225    }
226
227    ClassLoader cl = null;
228    if (path == null) {
229      try {
230        implClass = getClass().getClassLoader().loadClass(className);
231      } catch (ClassNotFoundException e) {
232        throw new IOException("No jar path specified for " + className);
233      }
234    } else {
235      cl =
236        CoprocessorClassLoader.getClassLoader(path, getClass().getClassLoader(), pathPrefix, conf);
237      try {
238        implClass = ((CoprocessorClassLoader) cl).loadClass(className, includedClassPrefixes);
239      } catch (ClassNotFoundException e) {
240        throw new IOException("Cannot load external coprocessor class " + className, e);
241      }
242    }
243
244    // load custom code for coprocessor
245    Thread currentThread = Thread.currentThread();
246    ClassLoader hostClassLoader = currentThread.getContextClassLoader();
247    try {
248      // switch temporarily to the thread classloader for custom CP
249      currentThread.setContextClassLoader(cl);
250      E cpInstance = checkAndLoadInstance(implClass, priority, conf);
251      return cpInstance;
252    } finally {
253      // restore the fresh (host) classloader
254      currentThread.setContextClassLoader(hostClassLoader);
255    }
256  }
257
258  public void load(Class<? extends C> implClass, int priority, Configuration conf)
259    throws IOException {
260    E env = checkAndLoadInstance(implClass, priority, conf);
261    coprocEnvironments.add(env);
262  }
263
264  /**
265   * @param implClass Implementation class
266   * @param priority  priority
267   * @param conf      configuration
268   * @throws java.io.IOException Exception
269   */
270  public E checkAndLoadInstance(Class<?> implClass, int priority, Configuration conf)
271    throws IOException {
272    // create the instance
273    C impl;
274    try {
275      impl = checkAndGetInstance(implClass);
276      if (impl == null) {
277        LOG.error("Cannot load coprocessor " + implClass.getSimpleName());
278        return null;
279      }
280    } catch (InstantiationException | IllegalAccessException e) {
281      throw new IOException(e);
282    }
283    // create the environment
284    E env = createEnvironment(impl, priority, loadSequence.incrementAndGet(), conf);
285    assert env instanceof BaseEnvironment;
286    ((BaseEnvironment<C>) env).startup();
287    // HBASE-4014: maintain list of loaded coprocessors for later crash analysis
288    // if server (master or regionserver) aborts.
289    coprocessorNames.add(implClass.getName());
290    return env;
291  }
292
293  /**
294   * Called when a new Coprocessor class is loaded
295   */
296  public abstract E createEnvironment(C instance, int priority, int sequence, Configuration conf);
297
298  /**
299   * Called when a new Coprocessor class needs to be loaded. Checks if type of the given class is
300   * what the corresponding host implementation expects. If it is of correct type, returns an
301   * instance of the coprocessor to be loaded. If not, returns null. If an exception occurs when
302   * trying to create instance of a coprocessor, it's passed up and eventually results into server
303   * aborting.
304   */
305  public abstract C checkAndGetInstance(Class<?> implClass)
306    throws InstantiationException, IllegalAccessException;
307
308  public void shutdown(E e) {
309    assert e instanceof BaseEnvironment;
310    if (LOG.isDebugEnabled()) {
311      LOG.debug("Stop coprocessor " + e.getInstance().getClass().getName());
312    }
313    ((BaseEnvironment<C>) e).shutdown();
314  }
315
316  /**
317   * Find coprocessors by full class name or simple name.
318   */
319  public C findCoprocessor(String className) {
320    for (E env : coprocEnvironments) {
321      if (
322        env.getInstance().getClass().getName().equals(className)
323          || env.getInstance().getClass().getSimpleName().equals(className)
324      ) {
325        return env.getInstance();
326      }
327    }
328    return null;
329  }
330
331  public <T extends C> T findCoprocessor(Class<T> cls) {
332    for (E env : coprocEnvironments) {
333      if (cls.isAssignableFrom(env.getInstance().getClass())) {
334        return (T) env.getInstance();
335      }
336    }
337    return null;
338  }
339
340  /**
341   * Find list of coprocessors that extend/implement the given class/interface
342   * @param cls the class/interface to look for
343   * @return the list of coprocessors, or null if not found
344   */
345  public <T extends C> List<T> findCoprocessors(Class<T> cls) {
346    ArrayList<T> ret = new ArrayList<>();
347
348    for (E env : coprocEnvironments) {
349      C cp = env.getInstance();
350
351      if (cp != null) {
352        if (cls.isAssignableFrom(cp.getClass())) {
353          ret.add((T) cp);
354        }
355      }
356    }
357    return ret;
358  }
359
360  /**
361   * Find a coprocessor environment by class name
362   * @param className the class name
363   * @return the coprocessor, or null if not found
364   */
365  public E findCoprocessorEnvironment(String className) {
366    for (E env : coprocEnvironments) {
367      if (
368        env.getInstance().getClass().getName().equals(className)
369          || env.getInstance().getClass().getSimpleName().equals(className)
370      ) {
371        return env;
372      }
373    }
374    return null;
375  }
376
377  /**
378   * Retrieves the set of classloaders used to instantiate Coprocessor classes defined in external
379   * jar files.
380   * @return A set of ClassLoader instances
381   */
382  Set<ClassLoader> getExternalClassLoaders() {
383    Set<ClassLoader> externalClassLoaders = new HashSet<>();
384    final ClassLoader systemClassLoader = this.getClass().getClassLoader();
385    for (E env : coprocEnvironments) {
386      ClassLoader cl = env.getInstance().getClass().getClassLoader();
387      if (cl != systemClassLoader) {
388        // do not include system classloader
389        externalClassLoaders.add(cl);
390      }
391    }
392    return externalClassLoaders;
393  }
394
395  /**
396   * Environment priority comparator. Coprocessors are chained in sorted order.
397   */
398  static class EnvironmentPriorityComparator implements Comparator<CoprocessorEnvironment> {
399    @Override
400    public int compare(final CoprocessorEnvironment env1, final CoprocessorEnvironment env2) {
401      if (env1.getPriority() < env2.getPriority()) {
402        return -1;
403      } else if (env1.getPriority() > env2.getPriority()) {
404        return 1;
405      }
406      if (env1.getLoadSequence() < env2.getLoadSequence()) {
407        return -1;
408      } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
409        return 1;
410      }
411      return 0;
412    }
413  }
414
415  protected void abortServer(final E environment, final Throwable e) {
416    abortServer(environment.getInstance().getClass().getName(), e);
417  }
418
419  protected void abortServer(final String coprocessorName, final Throwable e) {
420    String message = "The coprocessor " + coprocessorName + " threw " + e.toString();
421    LOG.error(message, e);
422    if (abortable != null) {
423      abortable.abort(message, e);
424    } else {
425      LOG.warn("No available Abortable, process was not aborted");
426    }
427  }
428
429  /**
430   * This is used by coprocessor hooks which are declared to throw IOException (or its subtypes).
431   * For such hooks, we should handle throwable objects depending on the Throwable's type. Those
432   * which are instances of IOException should be passed on to the client. This is in conformance
433   * with the HBase idiom regarding IOException: that it represents a circumstance that should be
434   * passed along to the client for its own handling. For example, a coprocessor that implements
435   * access controls would throw a subclass of IOException, such as AccessDeniedException, in its
436   * preGet() method to prevent an unauthorized client's performing a Get on a particular table.
437   * @param env Coprocessor Environment
438   * @param e   Throwable object thrown by coprocessor.
439   * @exception IOException Exception
440   */
441  // Note to devs: Class comments of all observers ({@link MasterObserver}, {@link WALObserver},
442  // etc) mention this nuance of our exception handling so that coprocessor can throw appropriate
443  // exceptions depending on situation. If any changes are made to this logic, make sure to
444  // update all classes' comments.
445  protected void handleCoprocessorThrowable(final E env, final Throwable e) throws IOException {
446    if (e instanceof IOException) {
447      throw (IOException) e;
448    }
449    // If we got here, e is not an IOException. A loaded coprocessor has a
450    // fatal bug, and the server (master or regionserver) should remove the
451    // faulty coprocessor from its set of active coprocessors. Setting
452    // 'hbase.coprocessor.abortonerror' to true will cause abortServer(),
453    // which may be useful in development and testing environments where
454    // 'failing fast' for error analysis is desired.
455    if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
456      // server is configured to abort.
457      abortServer(env, e);
458    } else {
459      // If available, pull a table name out of the environment
460      if (env instanceof RegionCoprocessorEnvironment) {
461        String tableName =
462          ((RegionCoprocessorEnvironment) env).getRegionInfo().getTable().getNameAsString();
463        LOG.error("Removing coprocessor '" + env.toString() + "' from table '" + tableName + "'",
464          e);
465      } else {
466        LOG.error("Removing coprocessor '" + env.toString() + "' from " + "environment", e);
467      }
468
469      coprocEnvironments.remove(env);
470      try {
471        shutdown(env);
472      } catch (Exception x) {
473        LOG.error("Uncaught exception when shutting down coprocessor '" + env.toString() + "'", x);
474      }
475      throw new DoNotRetryIOException("Coprocessor: '" + env.toString() + "' threw: '" + e
476        + "' and has been removed from the active " + "coprocessor set.", e);
477    }
478  }
479
480  /**
481   * Used to limit legacy handling to once per Coprocessor class per classloader.
482   */
483  private static final Set<Class<? extends Coprocessor>> legacyWarning =
484    new ConcurrentSkipListSet<>(new Comparator<Class<? extends Coprocessor>>() {
485      @Override
486      public int compare(Class<? extends Coprocessor> c1, Class<? extends Coprocessor> c2) {
487        if (c1.equals(c2)) {
488          return 0;
489        }
490        return c1.getName().compareTo(c2.getName());
491      }
492    });
493
494  /**
495   * Implementations defined function to get an observer of type {@code O} from a coprocessor of
496   * type {@code C}. Concrete implementations of CoprocessorHost define one getter for each observer
497   * they can handle. For e.g. RegionCoprocessorHost will use 3 getters, one for each of
498   * RegionObserver, EndpointObserver and BulkLoadObserver. These getters are used by
499   * {@code ObserverOperation} to get appropriate observer from the coprocessor.
500   */
501  @FunctionalInterface
502  public interface ObserverGetter<C, O> extends Function<C, Optional<O>> {
503  }
504
505  private abstract class ObserverOperation<O> extends ObserverContextImpl<E> {
506    ObserverGetter<C, O> observerGetter;
507
508    ObserverOperation(ObserverGetter<C, O> observerGetter) {
509      this(observerGetter, null);
510    }
511
512    ObserverOperation(ObserverGetter<C, O> observerGetter, User user) {
513      this(observerGetter, user, false);
514    }
515
516    ObserverOperation(ObserverGetter<C, O> observerGetter, boolean bypassable) {
517      this(observerGetter, null, bypassable);
518    }
519
520    ObserverOperation(ObserverGetter<C, O> observerGetter, User user, boolean bypassable) {
521      super(user != null ? user : RpcServer.getRequestUser().orElse(null), bypassable);
522      this.observerGetter = observerGetter;
523    }
524
525    abstract void callObserver() throws IOException;
526
527    protected void postEnvCall() {
528    }
529  }
530
531  // Can't derive ObserverOperation from ObserverOperationWithResult (R = Void) because then all
532  // ObserverCaller implementations will have to have a return statement.
533  // O = observer, E = environment, C = coprocessor, R=result type
534  public abstract class ObserverOperationWithoutResult<O> extends ObserverOperation<O> {
535    protected abstract void call(O observer) throws IOException;
536
537    public ObserverOperationWithoutResult(ObserverGetter<C, O> observerGetter) {
538      super(observerGetter);
539    }
540
541    public ObserverOperationWithoutResult(ObserverGetter<C, O> observerGetter, User user) {
542      super(observerGetter, user);
543    }
544
545    public ObserverOperationWithoutResult(ObserverGetter<C, O> observerGetter, User user,
546      boolean bypassable) {
547      super(observerGetter, user, bypassable);
548    }
549
550    /**
551     * In case of coprocessors which have many kinds of observers (for eg, {@link RegionCoprocessor}
552     * has BulkLoadObserver, RegionObserver, etc), some implementations may not need all observers,
553     * in which case they will return null for that observer's getter. We simply ignore such cases.
554     */
555    @Override
556    void callObserver() throws IOException {
557      Optional<O> observer = observerGetter.apply(getEnvironment().getInstance());
558      if (observer.isPresent()) {
559        call(observer.get());
560      }
561    }
562  }
563
564  public abstract class ObserverOperationWithResult<O, R> extends ObserverOperation<O> {
565    protected abstract R call(O observer) throws IOException;
566
567    private R result;
568
569    public ObserverOperationWithResult(ObserverGetter<C, O> observerGetter, R result) {
570      this(observerGetter, result, false);
571    }
572
573    public ObserverOperationWithResult(ObserverGetter<C, O> observerGetter, R result,
574      boolean bypassable) {
575      this(observerGetter, result, null, bypassable);
576    }
577
578    public ObserverOperationWithResult(ObserverGetter<C, O> observerGetter, R result, User user) {
579      this(observerGetter, result, user, false);
580    }
581
582    private ObserverOperationWithResult(ObserverGetter<C, O> observerGetter, R result, User user,
583      boolean bypassable) {
584      super(observerGetter, user, bypassable);
585      this.result = result;
586    }
587
588    protected R getResult() {
589      return this.result;
590    }
591
592    @Override
593    void callObserver() throws IOException {
594      Optional<O> observer = observerGetter.apply(getEnvironment().getInstance());
595      if (observer.isPresent()) {
596        result = call(observer.get());
597      }
598    }
599  }
600
601  //////////////////////////////////////////////////////////////////////////////////////////
602  // Functions to execute observer hooks and handle results (if any)
603  //////////////////////////////////////////////////////////////////////////////////////////
604
605  /**
606   * Do not call with an observerOperation that is null! Have the caller check.
607   */
608  protected <O, R> R execOperationWithResult(
609    final ObserverOperationWithResult<O, R> observerOperation) throws IOException {
610    boolean bypass = execOperation(observerOperation);
611    R result = observerOperation.getResult();
612    return bypass == observerOperation.isBypassable() ? result : null;
613  }
614
615  /**
616   * @return True if we are to bypass (Can only be <code>true</code> if
617   *         ObserverOperation#isBypassable().
618   */
619  protected <O> boolean execOperation(final ObserverOperation<O> observerOperation)
620    throws IOException {
621    boolean bypass = false;
622    if (observerOperation == null) {
623      return bypass;
624    }
625    List<E> envs = coprocEnvironments.get();
626    for (E env : envs) {
627      observerOperation.prepare(env);
628      Thread currentThread = Thread.currentThread();
629      ClassLoader cl = currentThread.getContextClassLoader();
630      try {
631        currentThread.setContextClassLoader(env.getClassLoader());
632        observerOperation.callObserver();
633      } catch (Throwable e) {
634        handleCoprocessorThrowable(env, e);
635      } finally {
636        currentThread.setContextClassLoader(cl);
637      }
638      // Internal to shouldBypass, it checks if obeserverOperation#isBypassable().
639      bypass |= observerOperation.shouldBypass();
640      observerOperation.postEnvCall();
641      if (bypass) {
642        // If CP says bypass, skip out w/o calling any following CPs; they might ruin our response.
643        // In hbase1, this used to be called 'complete'. In hbase2, we unite bypass and 'complete'.
644        break;
645      }
646    }
647    return bypass;
648  }
649
650  /**
651   * Coprocessor classes can be configured in any order, based on that priority is set and chained
652   * in a sorted order. Should be used preStop*() hooks i.e. when master/regionserver is going down.
653   * This function first calls coprocessor methods (using ObserverOperation.call()) and then
654   * shutdowns the environment in postEnvCall(). <br>
655   * Need to execute all coprocessor methods first then postEnvCall(), otherwise some coprocessors
656   * may remain shutdown if any exception occurs during next coprocessor execution which prevent
657   * master/regionserver stop or cluster shutdown. (Refer:
658   * <a href="https://issues.apache.org/jira/browse/HBASE-16663">HBASE-16663</a>
659   * @return true if bypaas coprocessor execution, false if not.
660   */
661  protected <O> boolean execShutdown(final ObserverOperation<O> observerOperation)
662    throws IOException {
663    if (observerOperation == null) return false;
664    boolean bypass = false;
665    List<E> envs = coprocEnvironments.get();
666    // Iterate the coprocessors and execute ObserverOperation's call()
667    for (E env : envs) {
668      observerOperation.prepare(env);
669      Thread currentThread = Thread.currentThread();
670      ClassLoader cl = currentThread.getContextClassLoader();
671      try {
672        currentThread.setContextClassLoader(env.getClassLoader());
673        observerOperation.callObserver();
674      } catch (Throwable e) {
675        handleCoprocessorThrowable(env, e);
676      } finally {
677        currentThread.setContextClassLoader(cl);
678      }
679      bypass |= observerOperation.shouldBypass();
680    }
681
682    // Iterate the coprocessors and execute ObserverOperation's postEnvCall()
683    for (E env : envs) {
684      observerOperation.prepare(env);
685      observerOperation.postEnvCall();
686    }
687    return bypass;
688  }
689}