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