View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.coprocessor;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.TreeSet;
31  import java.util.UUID;
32  import java.util.concurrent.ExecutorService;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.fs.Path;
39  import org.apache.hadoop.hbase.Abortable;
40  import org.apache.hadoop.hbase.Coprocessor;
41  import org.apache.hadoop.hbase.CoprocessorEnvironment;
42  import org.apache.hadoop.hbase.DoNotRetryIOException;
43  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
44  import org.apache.hadoop.hbase.HTableDescriptor;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.classification.InterfaceAudience;
47  import org.apache.hadoop.hbase.classification.InterfaceStability;
48  import org.apache.hadoop.hbase.client.Append;
49  import org.apache.hadoop.hbase.client.CoprocessorHConnection;
50  import org.apache.hadoop.hbase.client.Delete;
51  import org.apache.hadoop.hbase.client.Durability;
52  import org.apache.hadoop.hbase.client.Get;
53  import org.apache.hadoop.hbase.client.HConnection;
54  import org.apache.hadoop.hbase.client.HTable;
55  import org.apache.hadoop.hbase.client.HTableInterface;
56  import org.apache.hadoop.hbase.client.Increment;
57  import org.apache.hadoop.hbase.client.Put;
58  import org.apache.hadoop.hbase.client.Result;
59  import org.apache.hadoop.hbase.client.ResultScanner;
60  import org.apache.hadoop.hbase.client.Row;
61  import org.apache.hadoop.hbase.client.RowMutations;
62  import org.apache.hadoop.hbase.client.Scan;
63  import org.apache.hadoop.hbase.client.coprocessor.Batch;
64  import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
65  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
66  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
67  import org.apache.hadoop.hbase.util.Bytes;
68  import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
69  import org.apache.hadoop.hbase.util.SortedList;
70  import org.apache.hadoop.hbase.util.VersionInfo;
71  import org.apache.hadoop.io.MultipleIOException;
72  
73  import com.google.protobuf.Descriptors;
74  import com.google.protobuf.Message;
75  import com.google.protobuf.Service;
76  import com.google.protobuf.ServiceException;
77  
78  /**
79   * Provides the common setup framework and runtime services for coprocessor
80   * invocation from HBase services.
81   * @param <E> the specific environment extension that a concrete implementation
82   * provides
83   */
84  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
85  @InterfaceStability.Evolving
86  public abstract class CoprocessorHost<E extends CoprocessorEnvironment> {
87    public static final String REGION_COPROCESSOR_CONF_KEY =
88        "hbase.coprocessor.region.classes";
89    public static final String REGIONSERVER_COPROCESSOR_CONF_KEY =
90        "hbase.coprocessor.regionserver.classes";
91    public static final String USER_REGION_COPROCESSOR_CONF_KEY =
92        "hbase.coprocessor.user.region.classes";
93    public static final String MASTER_COPROCESSOR_CONF_KEY =
94        "hbase.coprocessor.master.classes";
95    public static final String WAL_COPROCESSOR_CONF_KEY =
96      "hbase.coprocessor.wal.classes";
97    public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror";
98    public static final boolean DEFAULT_ABORT_ON_ERROR = true;
99    public static final String COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.enabled";
100   public static final boolean DEFAULT_COPROCESSORS_ENABLED = true;
101   public static final String USER_COPROCESSORS_ENABLED_CONF_KEY =
102     "hbase.coprocessor.user.enabled";
103   public static final boolean DEFAULT_USER_COPROCESSORS_ENABLED = true;
104 
105   protected static final Log LOG = LogFactory.getLog(CoprocessorHost.class);
106   protected Abortable abortable;
107   /** Ordered set of loaded coprocessors with lock */
108   protected SortedList<E> coprocessors =
109       new SortedList<E>(new EnvironmentPriorityComparator());
110   protected Configuration conf;
111   // unique file prefix to use for local copies of jars when classloading
112   protected String pathPrefix;
113   protected AtomicInteger loadSequence = new AtomicInteger();
114 
115   public CoprocessorHost(Abortable abortable) {
116     this.abortable = abortable;
117     this.pathPrefix = UUID.randomUUID().toString();
118   }
119 
120   /**
121    * Not to be confused with the per-object _coprocessors_ (above),
122    * coprocessorNames is static and stores the set of all coprocessors ever
123    * loaded by any thread in this JVM. It is strictly additive: coprocessors are
124    * added to coprocessorNames, by loadInstance() but are never removed, since
125    * the intention is to preserve a history of all loaded coprocessors for
126    * diagnosis in case of server crash (HBASE-4014).
127    */
128   private static Set<String> coprocessorNames =
129       Collections.synchronizedSet(new HashSet<String>());
130 
131   public static Set<String> getLoadedCoprocessors() {
132     synchronized (coprocessorNames) {
133       return new HashSet(coprocessorNames);
134     }
135   }
136 
137   /**
138    * Used to create a parameter to the HServerLoad constructor so that
139    * HServerLoad can provide information about the coprocessors loaded by this
140    * regionserver.
141    * (HBASE-4070: Improve region server metrics to report loaded coprocessors
142    * to master).
143    */
144   public Set<String> getCoprocessors() {
145     Set<String> returnValue = new TreeSet<String>();
146     for (CoprocessorEnvironment e: coprocessors) {
147       returnValue.add(e.getInstance().getClass().getSimpleName());
148     }
149     return returnValue;
150   }
151 
152   /**
153    * Load system coprocessors once only. Read the class names from configuration.
154    * Called by constructor.
155    */
156   protected void loadSystemCoprocessors(Configuration conf, String confKey) {
157     boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY,
158       DEFAULT_COPROCESSORS_ENABLED);
159     if (!coprocessorsEnabled) {
160       return;
161     }
162 
163     Class<?> implClass = null;
164 
165     // load default coprocessors from configure file
166     String[] defaultCPClasses = conf.getStrings(confKey);
167     if (defaultCPClasses == null || defaultCPClasses.length == 0)
168       return;
169 
170     int priority = Coprocessor.PRIORITY_SYSTEM;
171     for (String className : defaultCPClasses) {
172       className = className.trim();
173       if (findCoprocessor(className) != null) {
174         // If already loaded will just continue
175         LOG.warn("Attempted duplicate loading of " + className + "; skipped");
176         continue;
177       }
178       ClassLoader cl = this.getClass().getClassLoader();
179       Thread.currentThread().setContextClassLoader(cl);
180       try {
181         implClass = cl.loadClass(className);
182         // Add coprocessors as we go to guard against case where a coprocessor is specified twice
183         // in the configuration
184         this.coprocessors.add(loadInstance(implClass, Coprocessor.PRIORITY_SYSTEM, conf));
185         LOG.info("System coprocessor " + className + " was loaded " +
186             "successfully with priority (" + priority++ + ").");
187       } catch (Throwable t) {
188         // We always abort if system coprocessors cannot be loaded
189         abortServer(className, t);
190       }
191     }
192   }
193 
194   /**
195    * Load a coprocessor implementation into the host
196    * @param path path to implementation jar
197    * @param className the main class name
198    * @param priority chaining priority
199    * @param conf configuration for coprocessor
200    * @throws java.io.IOException Exception
201    */
202   public E load(Path path, String className, int priority,
203       Configuration conf) throws IOException {
204     Class<?> implClass = null;
205     LOG.debug("Loading coprocessor class " + className + " with path " +
206         path + " and priority " + priority);
207 
208     ClassLoader cl = null;
209     if (path == null) {
210       try {
211         implClass = getClass().getClassLoader().loadClass(className);
212       } catch (ClassNotFoundException e) {
213         throw new IOException("No jar path specified for " + className);
214       }
215     } else {
216       cl = CoprocessorClassLoader.getClassLoader(
217         path, getClass().getClassLoader(), pathPrefix, conf);
218       try {
219         implClass = cl.loadClass(className);
220       } catch (ClassNotFoundException e) {
221         throw new IOException("Cannot load external coprocessor class " + className, e);
222       }
223     }
224 
225     //load custom code for coprocessor
226     Thread currentThread = Thread.currentThread();
227     ClassLoader hostClassLoader = currentThread.getContextClassLoader();
228     try{
229       // switch temporarily to the thread classloader for custom CP
230       currentThread.setContextClassLoader(cl);
231       E cpInstance = loadInstance(implClass, priority, conf);
232       return cpInstance;
233     } finally {
234       // restore the fresh (host) classloader
235       currentThread.setContextClassLoader(hostClassLoader);
236     }
237   }
238 
239   /**
240    * @param implClass Implementation class
241    * @param priority priority
242    * @param conf configuration
243    * @throws java.io.IOException Exception
244    */
245   public void load(Class<?> implClass, int priority, Configuration conf)
246       throws IOException {
247     E env = loadInstance(implClass, priority, conf);
248     coprocessors.add(env);
249   }
250 
251   /**
252    * @param implClass Implementation class
253    * @param priority priority
254    * @param conf configuration
255    * @throws java.io.IOException Exception
256    */
257   public E loadInstance(Class<?> implClass, int priority, Configuration conf)
258       throws IOException {
259     if (!Coprocessor.class.isAssignableFrom(implClass)) {
260       throw new IOException("Configured class " + implClass.getName() + " must implement "
261           + Coprocessor.class.getName() + " interface ");
262     }
263 
264     // create the instance
265     Coprocessor impl;
266     Object o = null;
267     try {
268       o = implClass.newInstance();
269       impl = (Coprocessor)o;
270     } catch (InstantiationException e) {
271       throw new IOException(e);
272     } catch (IllegalAccessException e) {
273       throw new IOException(e);
274     }
275     // create the environment
276     E env = createEnvironment(implClass, impl, priority, loadSequence.incrementAndGet(), conf);
277     if (env instanceof Environment) {
278       ((Environment)env).startup();
279     }
280     // HBASE-4014: maintain list of loaded coprocessors for later crash analysis
281     // if server (master or regionserver) aborts.
282     coprocessorNames.add(implClass.getName());
283     return env;
284   }
285 
286   /**
287    * Called when a new Coprocessor class is loaded
288    */
289   public abstract E createEnvironment(Class<?> implClass, Coprocessor instance,
290       int priority, int sequence, Configuration conf);
291 
292   public void shutdown(CoprocessorEnvironment e) {
293     if (e instanceof Environment) {
294       if (LOG.isDebugEnabled()) {
295         LOG.debug("Stop coprocessor " + e.getInstance().getClass().getName());
296       }
297       ((Environment)e).shutdown();
298     } else {
299       LOG.warn("Shutdown called on unknown environment: "+
300           e.getClass().getName());
301     }
302   }
303 
304   /**
305    * Find a coprocessor implementation by class name
306    * @param className the class name
307    * @return the coprocessor, or null if not found
308    */
309   public Coprocessor findCoprocessor(String className) {
310     for (E env: coprocessors) {
311       if (env.getInstance().getClass().getName().equals(className) ||
312           env.getInstance().getClass().getSimpleName().equals(className)) {
313         return env.getInstance();
314       }
315     }
316     return null;
317   }
318 
319   /**
320    * Find list of coprocessors that extend/implement the given class/interface
321    * @param cls the class/interface to look for
322    * @return the list of coprocessors, or null if not found
323    */
324   public <T extends Coprocessor> List<T> findCoprocessors(Class<T> cls) {
325     ArrayList<T> ret = new ArrayList<T>();
326 
327     for (E env: coprocessors) {
328       Coprocessor cp = env.getInstance();
329 
330       if(cp != null) {
331         if (cls.isAssignableFrom(cp.getClass())) {
332           ret.add((T)cp);
333         }
334       }
335     }
336     return ret;
337   }
338 
339   /**
340    * Find a coprocessor environment by class name
341    * @param className the class name
342    * @return the coprocessor, or null if not found
343    */
344   public CoprocessorEnvironment findCoprocessorEnvironment(String className) {
345     for (E env: coprocessors) {
346       if (env.getInstance().getClass().getName().equals(className) ||
347           env.getInstance().getClass().getSimpleName().equals(className)) {
348         return env;
349       }
350     }
351     return null;
352   }
353 
354   /**
355    * Retrieves the set of classloaders used to instantiate Coprocessor classes defined in external
356    * jar files.
357    * @return A set of ClassLoader instances
358    */
359   Set<ClassLoader> getExternalClassLoaders() {
360     Set<ClassLoader> externalClassLoaders = new HashSet<ClassLoader>();
361     final ClassLoader systemClassLoader = this.getClass().getClassLoader();
362     for (E env : coprocessors) {
363       ClassLoader cl = env.getInstance().getClass().getClassLoader();
364       if (cl != systemClassLoader ){
365         //do not include system classloader
366         externalClassLoaders.add(cl);
367       }
368     }
369     return externalClassLoaders;
370   }
371 
372   /**
373    * Environment priority comparator.
374    * Coprocessors are chained in sorted order.
375    */
376   static class EnvironmentPriorityComparator
377       implements Comparator<CoprocessorEnvironment> {
378     @Override
379     public int compare(final CoprocessorEnvironment env1,
380         final CoprocessorEnvironment env2) {
381       if (env1.getPriority() < env2.getPriority()) {
382         return -1;
383       } else if (env1.getPriority() > env2.getPriority()) {
384         return 1;
385       }
386       if (env1.getLoadSequence() < env2.getLoadSequence()) {
387         return -1;
388       } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
389         return 1;
390       }
391       return 0;
392     }
393   }
394 
395   /**
396    * Encapsulation of the environment of each coprocessor
397    */
398   public static class Environment implements CoprocessorEnvironment {
399 
400     /**
401      * A wrapper for HTable. Can be used to restrict privilege.
402      *
403      * Currently it just helps to track tables opened by a Coprocessor and
404      * facilitate close of them if it is aborted.
405      *
406      * We also disallow row locking.
407      *
408      * There is nothing now that will stop a coprocessor from using HTable
409      * objects directly instead of this API, but in the future we intend to
410      * analyze coprocessor implementations as they are loaded and reject those
411      * which attempt to use objects and methods outside the Environment
412      * sandbox.
413      */
414     class HTableWrapper implements HTableInterface {
415 
416       private TableName tableName;
417       private HTable table;
418       private HConnection connection;
419 
420       public HTableWrapper(TableName tableName, HConnection connection, ExecutorService pool)
421           throws IOException {
422         this.tableName = tableName;
423         this.table = new HTable(tableName, connection, pool);
424         this.connection = connection;
425         openTables.add(this);
426       }
427 
428       void internalClose() throws IOException {
429         List<IOException> exceptions = new ArrayList<IOException>(2);
430         try {
431         table.close();
432         } catch (IOException e) {
433           exceptions.add(e);
434         }
435         try {
436           // have to self-manage our connection, as per the HTable contract
437           if (this.connection != null) {
438             this.connection.close();
439           }
440         } catch (IOException e) {
441           exceptions.add(e);
442         }
443         if (!exceptions.isEmpty()) {
444           throw MultipleIOException.createIOException(exceptions);
445         }
446       }
447 
448       public Configuration getConfiguration() {
449         return table.getConfiguration();
450       }
451 
452       public void close() throws IOException {
453         try {
454           internalClose();
455         } finally {
456           openTables.remove(this);
457         }
458       }
459 
460       public Result getRowOrBefore(byte[] row, byte[] family)
461           throws IOException {
462         return table.getRowOrBefore(row, family);
463       }
464 
465       public Result get(Get get) throws IOException {
466         return table.get(get);
467       }
468 
469       public boolean exists(Get get) throws IOException {
470         return table.exists(get);
471       }
472 
473       public Boolean[] exists(List<Get> gets) throws IOException{
474         return table.exists(gets);
475       }
476 
477       public void put(Put put) throws IOException {
478         table.put(put);
479       }
480 
481       public void put(List<Put> puts) throws IOException {
482         table.put(puts);
483       }
484 
485       public void delete(Delete delete) throws IOException {
486         table.delete(delete);
487       }
488 
489       public void delete(List<Delete> deletes) throws IOException {
490         table.delete(deletes);
491       }
492 
493       public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
494           byte[] value, Put put) throws IOException {
495         return table.checkAndPut(row, family, qualifier, value, put);
496       }
497 
498       public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
499           byte[] value, Delete delete) throws IOException {
500         return table.checkAndDelete(row, family, qualifier, value, delete);
501       }
502 
503       public long incrementColumnValue(byte[] row, byte[] family,
504           byte[] qualifier, long amount) throws IOException {
505         return table.incrementColumnValue(row, family, qualifier, amount);
506       }
507 
508       public long incrementColumnValue(byte[] row, byte[] family,
509           byte[] qualifier, long amount, Durability durability)
510           throws IOException {
511         return table.incrementColumnValue(row, family, qualifier, amount,
512             durability);
513       }
514 
515       @Override
516       public Result append(Append append) throws IOException {
517         return table.append(append);
518       }
519 
520       @Override
521       public Result increment(Increment increment) throws IOException {
522         return table.increment(increment);
523       }
524 
525       public void flushCommits() throws IOException {
526         table.flushCommits();
527       }
528 
529       public boolean isAutoFlush() {
530         return table.isAutoFlush();
531       }
532 
533       public ResultScanner getScanner(Scan scan) throws IOException {
534         return table.getScanner(scan);
535       }
536 
537       public ResultScanner getScanner(byte[] family) throws IOException {
538         return table.getScanner(family);
539       }
540 
541       public ResultScanner getScanner(byte[] family, byte[] qualifier)
542           throws IOException {
543         return table.getScanner(family, qualifier);
544       }
545 
546       public HTableDescriptor getTableDescriptor() throws IOException {
547         return table.getTableDescriptor();
548       }
549 
550       @Override
551       public byte[] getTableName() {
552         return tableName.getName();
553       }
554 
555       @Override
556       public TableName getName() {
557         return table.getName();
558       }
559 
560       @Override
561       public void batch(List<? extends Row> actions, Object[] results)
562           throws IOException, InterruptedException {
563         table.batch(actions, results);
564       }
565 
566       /**
567        * {@inheritDoc}
568        * @deprecated If any exception is thrown by one of the actions, there is no way to
569        * retrieve the partially executed results. Use {@link #batch(List, Object[])} instead.
570        */
571       @Override
572       public Object[] batch(List<? extends Row> actions)
573           throws IOException, InterruptedException {
574         return table.batch(actions);
575       }
576 
577       @Override
578       public <R> void batchCallback(List<? extends Row> actions, Object[] results,
579           Batch.Callback<R> callback) throws IOException, InterruptedException {
580         table.batchCallback(actions, results, callback);
581       }
582 
583       /**
584        * {@inheritDoc}
585        * @deprecated If any exception is thrown by one of the actions, there is no way to
586        * retrieve the partially executed results. Use 
587        * {@link #batchCallback(List, Object[], org.apache.hadoop.hbase.client.coprocessor.Batch.Callback)}
588        * instead.
589        */
590       @Override
591       public <R> Object[] batchCallback(List<? extends Row> actions,
592           Batch.Callback<R> callback) throws IOException, InterruptedException {
593         return table.batchCallback(actions, callback);
594       }
595 
596       @Override
597       public Result[] get(List<Get> gets) throws IOException {
598         return table.get(gets);
599       }
600 
601       @Override
602       public CoprocessorRpcChannel coprocessorService(byte[] row) {
603         return table.coprocessorService(row);
604       }
605 
606       @Override
607       public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
608           byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
609           throws ServiceException, Throwable {
610         return table.coprocessorService(service, startKey, endKey, callable);
611       }
612 
613       @Override
614       public <T extends Service, R> void coprocessorService(Class<T> service,
615           byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
616           throws ServiceException, Throwable {
617         table.coprocessorService(service, startKey, endKey, callable, callback);
618       }
619 
620       @Override
621       public void mutateRow(RowMutations rm) throws IOException {
622         table.mutateRow(rm);
623       }
624 
625       @Override
626       public void setAutoFlush(boolean autoFlush) {
627         table.setAutoFlush(autoFlush, autoFlush);
628       }
629 
630       @Override
631       public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) {
632         table.setAutoFlush(autoFlush, clearBufferOnFail);
633       }
634 
635       @Override
636       public void setAutoFlushTo(boolean autoFlush) {
637         table.setAutoFlushTo(autoFlush);
638       }
639 
640       @Override
641       public long getWriteBufferSize() {
642          return table.getWriteBufferSize();
643       }
644 
645       @Override
646       public void setWriteBufferSize(long writeBufferSize) throws IOException {
647         table.setWriteBufferSize(writeBufferSize);
648       }
649 
650       @Override
651       public long incrementColumnValue(byte[] row, byte[] family,
652           byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
653         return table.incrementColumnValue(row, family, qualifier, amount, writeToWAL);
654       }
655 
656       @Override
657       public <R extends Message> Map<byte[], R> batchCoprocessorService(
658           Descriptors.MethodDescriptor method, Message request, byte[] startKey,
659           byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
660         return table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype);
661       }
662 
663       @Override
664       public <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor method,
665           Message request, byte[] startKey, byte[] endKey, R responsePrototype,
666           Callback<R> callback) throws ServiceException, Throwable {
667         table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype,
668             callback);
669       }
670 
671       @Override
672       public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
673           CompareOp compareOp, byte[] value, RowMutations mutation) throws IOException {
674         return table.checkAndMutate(row, family, qualifier, compareOp, value, mutation);
675       }
676     }
677 
678     /** The coprocessor */
679     public Coprocessor impl;
680     /** Chaining priority */
681     protected int priority = Coprocessor.PRIORITY_USER;
682     /** Current coprocessor state */
683     Coprocessor.State state = Coprocessor.State.UNINSTALLED;
684     /** Accounting for tables opened by the coprocessor */
685     protected List<HTableInterface> openTables =
686       Collections.synchronizedList(new ArrayList<HTableInterface>());
687     private int seq;
688     private Configuration conf;
689     private ClassLoader classLoader;
690 
691     /**
692      * Constructor
693      * @param impl the coprocessor instance
694      * @param priority chaining priority
695      */
696     public Environment(final Coprocessor impl, final int priority,
697         final int seq, final Configuration conf) {
698       this.impl = impl;
699       this.classLoader = impl.getClass().getClassLoader();
700       this.priority = priority;
701       this.state = Coprocessor.State.INSTALLED;
702       this.seq = seq;
703       this.conf = conf;
704     }
705 
706     /** Initialize the environment */
707     public void startup() throws IOException {
708       if (state == Coprocessor.State.INSTALLED ||
709           state == Coprocessor.State.STOPPED) {
710         state = Coprocessor.State.STARTING;
711         Thread currentThread = Thread.currentThread();
712         ClassLoader hostClassLoader = currentThread.getContextClassLoader();
713         try {
714           currentThread.setContextClassLoader(this.getClassLoader());
715           impl.start(this);
716           state = Coprocessor.State.ACTIVE;
717         } finally {
718           currentThread.setContextClassLoader(hostClassLoader);
719         }
720       } else {
721         LOG.warn("Not starting coprocessor "+impl.getClass().getName()+
722             " because not inactive (state="+state.toString()+")");
723       }
724     }
725 
726     /** Clean up the environment */
727     protected void shutdown() {
728       if (state == Coprocessor.State.ACTIVE) {
729         state = Coprocessor.State.STOPPING;
730         Thread currentThread = Thread.currentThread();
731         ClassLoader hostClassLoader = currentThread.getContextClassLoader();
732         try {
733           currentThread.setContextClassLoader(this.getClassLoader());
734           impl.stop(this);
735           state = Coprocessor.State.STOPPED;
736         } catch (IOException ioe) {
737           LOG.error("Error stopping coprocessor "+impl.getClass().getName(), ioe);
738         } finally {
739           currentThread.setContextClassLoader(hostClassLoader);
740         }
741       } else {
742         LOG.warn("Not stopping coprocessor "+impl.getClass().getName()+
743             " because not active (state="+state.toString()+")");
744       }
745       synchronized (openTables) {
746         // clean up any table references
747         for (HTableInterface table: openTables) {
748           try {
749             ((HTableWrapper)table).internalClose();
750           } catch (IOException e) {
751             // nothing can be done here
752             LOG.warn("Failed to close " +
753                 Bytes.toStringBinary(table.getTableName()), e);
754           }
755         }
756       }
757     }
758 
759     @Override
760     public Coprocessor getInstance() {
761       return impl;
762     }
763 
764     @Override
765     public ClassLoader getClassLoader() {
766       return classLoader;
767     }
768 
769     @Override
770     public int getPriority() {
771       return priority;
772     }
773 
774     @Override
775     public int getLoadSequence() {
776       return seq;
777     }
778 
779     /** @return the coprocessor environment version */
780     @Override
781     public int getVersion() {
782       return Coprocessor.VERSION;
783     }
784 
785     /** @return the HBase release */
786     @Override
787     public String getHBaseVersion() {
788       return VersionInfo.getVersion();
789     }
790 
791     @Override
792     public Configuration getConfiguration() {
793       return conf;
794     }
795 
796     /**
797      * Open a table from within the Coprocessor environment
798      * @param tableName the table name
799      * @return an interface for manipulating the table
800      * @exception java.io.IOException Exception
801      */
802     @Override
803     public HTableInterface getTable(TableName tableName) throws IOException {
804       return this.getTable(tableName, HTable.getDefaultExecutor(getConfiguration()));
805     }
806 
807     /**
808      * Open a table from within the Coprocessor environment
809      * @param tableName the table name
810      * @return an interface for manipulating the table
811      * @exception java.io.IOException Exception
812      */
813     @Override
814     public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException {
815       return new HTableWrapper(tableName, CoprocessorHConnection.getConnectionForEnvironment(this),
816           pool);
817     }
818   }
819 
820   protected void abortServer(final CoprocessorEnvironment environment, final Throwable e) {
821     abortServer(environment.getInstance().getClass().getName(), e);
822   }
823 
824   protected void abortServer(final String coprocessorName, final Throwable e) {
825     String message = "The coprocessor " + coprocessorName + " threw " + e.toString();
826     LOG.error(message, e);
827     if (abortable != null) {
828       abortable.abort(message, e);
829     } else {
830       LOG.warn("No available Abortable, process was not aborted");
831     }
832   }
833 
834   /**
835    * This is used by coprocessor hooks which are declared to throw IOException
836    * (or its subtypes). For such hooks, we should handle throwable objects
837    * depending on the Throwable's type. Those which are instances of
838    * IOException should be passed on to the client. This is in conformance with
839    * the HBase idiom regarding IOException: that it represents a circumstance
840    * that should be passed along to the client for its own handling. For
841    * example, a coprocessor that implements access controls would throw a
842    * subclass of IOException, such as AccessDeniedException, in its preGet()
843    * method to prevent an unauthorized client's performing a Get on a particular
844    * table.
845    * @param env Coprocessor Environment
846    * @param e Throwable object thrown by coprocessor.
847    * @exception IOException Exception
848    */
849   protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
850       throws IOException {
851     if (e instanceof IOException) {
852       throw (IOException)e;
853     }
854     // If we got here, e is not an IOException. A loaded coprocessor has a
855     // fatal bug, and the server (master or regionserver) should remove the
856     // faulty coprocessor from its set of active coprocessors. Setting
857     // 'hbase.coprocessor.abortonerror' to true will cause abortServer(),
858     // which may be useful in development and testing environments where
859     // 'failing fast' for error analysis is desired.
860     if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
861       // server is configured to abort.
862       abortServer(env, e);
863     } else {
864       LOG.error("Removing coprocessor '" + env.toString() + "' from " +
865           "environment because it threw:  " + e,e);
866       coprocessors.remove(env);
867       try {
868         shutdown(env);
869       } catch (Exception x) {
870         LOG.error("Uncaught exception when shutting down coprocessor '"
871             + env.toString() + "'", x);
872       }
873       throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
874           "' threw: '" + e + "' and has been removed from the active " +
875           "coprocessor set.", e);
876     }
877   }
878 }