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.regionserver;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.NavigableSet;
29  import java.util.UUID;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ConcurrentMap;
32  import java.util.regex.Matcher;
33  
34  import org.apache.commons.collections.map.AbstractReferenceMap;
35  import org.apache.commons.collections.map.ReferenceMap;
36  import org.apache.commons.lang.ClassUtils;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
40  import org.apache.hadoop.hbase.classification.InterfaceAudience;
41  import org.apache.hadoop.hbase.classification.InterfaceStability;
42  import org.apache.hadoop.conf.Configuration;
43  import org.apache.hadoop.fs.FileSystem;
44  import org.apache.hadoop.fs.Path;
45  import org.apache.hadoop.hbase.Cell;
46  import org.apache.hadoop.hbase.Coprocessor;
47  import org.apache.hadoop.hbase.CoprocessorEnvironment;
48  import org.apache.hadoop.hbase.HBaseConfiguration;
49  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
50  import org.apache.hadoop.hbase.HConstants;
51  import org.apache.hadoop.hbase.HRegionInfo;
52  import org.apache.hadoop.hbase.HTableDescriptor;
53  import org.apache.hadoop.hbase.client.Append;
54  import org.apache.hadoop.hbase.client.Delete;
55  import org.apache.hadoop.hbase.client.Durability;
56  import org.apache.hadoop.hbase.client.Get;
57  import org.apache.hadoop.hbase.client.Increment;
58  import org.apache.hadoop.hbase.client.Mutation;
59  import org.apache.hadoop.hbase.client.Put;
60  import org.apache.hadoop.hbase.client.Result;
61  import org.apache.hadoop.hbase.client.Scan;
62  import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
63  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
64  import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
65  import org.apache.hadoop.hbase.coprocessor.EndpointObserver;
66  import org.apache.hadoop.hbase.coprocessor.ObserverContext;
67  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
68  import org.apache.hadoop.hbase.coprocessor.RegionObserver;
69  import org.apache.hadoop.hbase.coprocessor.RegionObserver.MutationType;
70  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
71  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
72  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
73  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
74  import org.apache.hadoop.hbase.io.Reference;
75  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
76  import org.apache.hadoop.hbase.regionserver.HRegion.Operation;
77  import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest;
78  import org.apache.hadoop.hbase.regionserver.wal.HLogKey;
79  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
80  import org.apache.hadoop.hbase.util.BoundedConcurrentLinkedQueue;
81  import org.apache.hadoop.hbase.util.Bytes;
82  import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
83  import org.apache.hadoop.hbase.util.Pair;
84  
85  import com.google.common.collect.ImmutableList;
86  import com.google.common.collect.Lists;
87  import com.google.protobuf.Message;
88  import com.google.protobuf.Service;
89  
90  /**
91   * Implements the coprocessor environment and runtime support for coprocessors
92   * loaded within a {@link HRegion}.
93   */
94  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
95  @InterfaceStability.Evolving
96  public class RegionCoprocessorHost
97      extends CoprocessorHost<RegionCoprocessorHost.RegionEnvironment> {
98  
99    private static final Log LOG = LogFactory.getLog(RegionCoprocessorHost.class);
100   // The shared data map
101   private static ReferenceMap sharedDataMap =
102       new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
103 
104   // optimization: no need to call postScannerFilterRow, if no coprocessor implements it
105   private final boolean hasCustomPostScannerFilterRow;
106 
107   /**
108    * 
109    * Encapsulation of the environment of each coprocessor
110    */
111   static class RegionEnvironment extends CoprocessorHost.Environment
112       implements RegionCoprocessorEnvironment {
113 
114     private HRegion region;
115     private RegionServerServices rsServices;
116     ConcurrentMap<String, Object> sharedData;
117     private static final int LATENCY_BUFFER_SIZE = 100;
118     private final BoundedConcurrentLinkedQueue<Long> coprocessorTimeNanos =
119         new BoundedConcurrentLinkedQueue<Long>(LATENCY_BUFFER_SIZE);
120 
121     /**
122      * Constructor
123      * @param impl the coprocessor instance
124      * @param priority chaining priority
125      */
126     public RegionEnvironment(final Coprocessor impl, final int priority,
127         final int seq, final Configuration conf, final HRegion region,
128         final RegionServerServices services, final ConcurrentMap<String, Object> sharedData) {
129       super(impl, priority, seq, conf);
130       this.region = region;
131       this.rsServices = services;
132       this.sharedData = sharedData;
133     }
134 
135     /** @return the region */
136     @Override
137     public HRegion getRegion() {
138       return region;
139     }
140 
141     /** @return reference to the region server services */
142     @Override
143     public RegionServerServices getRegionServerServices() {
144       return rsServices;
145     }
146 
147     public void shutdown() {
148       super.shutdown();
149     }
150 
151     @Override
152     public ConcurrentMap<String, Object> getSharedData() {
153       return sharedData;
154     }
155 
156     public void offerExecutionLatency(long latencyNanos) {
157       coprocessorTimeNanos.offer(latencyNanos);
158     }
159 
160     public Collection<Long> getExecutionLatenciesNanos() {
161       final List<Long> latencies = Lists.newArrayListWithCapacity(coprocessorTimeNanos.size());
162       coprocessorTimeNanos.drainTo(latencies);
163       return latencies;
164     }
165 
166     @Override
167     public HRegionInfo getRegionInfo() {
168       return region.getRegionInfo();
169     }
170 
171   }
172 
173   static class TableCoprocessorAttribute {
174     private Path path;
175     private String className;
176     private int priority;
177     private Configuration conf;
178 
179     public TableCoprocessorAttribute(Path path, String className, int priority,
180         Configuration conf) {
181       this.path = path;
182       this.className = className;
183       this.priority = priority;
184       this.conf = conf;
185     }
186 
187     public Path getPath() {
188       return path;
189     }
190 
191     public String getClassName() {
192       return className;
193     }
194 
195     public int getPriority() {
196       return priority;
197     }
198 
199     public Configuration getConf() {
200       return conf;
201     }
202   }
203 
204   /** The region server services */
205   RegionServerServices rsServices;
206   /** The region */
207   HRegion region;
208 
209   /**
210    * Constructor
211    * @param region the region
212    * @param rsServices interface to available region server functionality
213    * @param conf the configuration
214    */
215   public RegionCoprocessorHost(final HRegion region,
216       final RegionServerServices rsServices, final Configuration conf) {
217     super(rsServices);
218     this.conf = conf;
219     this.rsServices = rsServices;
220     this.region = region;
221     this.pathPrefix = Integer.toString(this.region.getRegionInfo().hashCode());
222 
223     // load system default cp's from configuration.
224     loadSystemCoprocessors(conf, REGION_COPROCESSOR_CONF_KEY);
225 
226     // load system default cp's for user tables from configuration.
227     if (!region.getRegionInfo().getTable().isSystemTable()) {
228       loadSystemCoprocessors(conf, USER_REGION_COPROCESSOR_CONF_KEY);
229     }
230 
231     // load Coprocessor From HDFS
232     loadTableCoprocessors(conf);
233 
234     // now check whether any coprocessor implements postScannerFilterRow
235     boolean hasCustomPostScannerFilterRow = false;
236     out: for (RegionEnvironment env: coprocessors) {
237       if (env.getInstance() instanceof RegionObserver) {
238         Class<?> clazz = env.getInstance().getClass();
239         for(;;) {
240           if (clazz == null) {
241             // we must have directly implemented RegionObserver
242             hasCustomPostScannerFilterRow = true;
243             break out;
244           }
245           if (clazz == BaseRegionObserver.class) {
246             // we reached BaseRegionObserver, try next coprocessor
247             break;
248           }
249           try {
250             clazz.getDeclaredMethod("postScannerFilterRow", ObserverContext.class,
251               InternalScanner.class, byte[].class, int.class, short.class, boolean.class);
252             // this coprocessor has a custom version of postScannerFilterRow
253             hasCustomPostScannerFilterRow = true;
254             break out;
255           } catch (NoSuchMethodException ignore) {
256           }
257           clazz = clazz.getSuperclass();
258         }
259       }
260     }
261     this.hasCustomPostScannerFilterRow = hasCustomPostScannerFilterRow;
262   }
263 
264   static List<TableCoprocessorAttribute> getTableCoprocessorAttrsFromSchema(Configuration conf,
265       HTableDescriptor htd) {
266     List<TableCoprocessorAttribute> result = Lists.newArrayList();
267     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e: htd.getValues().entrySet()) {
268       String key = Bytes.toString(e.getKey().get()).trim();
269       if (HConstants.CP_HTD_ATTR_KEY_PATTERN.matcher(key).matches()) {
270         String spec = Bytes.toString(e.getValue().get()).trim();
271         // found one
272         try {
273           Matcher matcher = HConstants.CP_HTD_ATTR_VALUE_PATTERN.matcher(spec);
274           if (matcher.matches()) {
275             // jar file path can be empty if the cp class can be loaded
276             // from class loader.
277             Path path = matcher.group(1).trim().isEmpty() ?
278                 null : new Path(matcher.group(1).trim());
279             String className = matcher.group(2).trim();
280             if (className.isEmpty()) {
281               LOG.error("Malformed table coprocessor specification: key=" +
282                 key + ", spec: " + spec);
283               continue;
284             }
285             int priority = matcher.group(3).trim().isEmpty() ?
286                 Coprocessor.PRIORITY_USER : Integer.valueOf(matcher.group(3));
287             String cfgSpec = null;
288             try {
289               cfgSpec = matcher.group(4);
290             } catch (IndexOutOfBoundsException ex) {
291               // ignore
292             }
293             Configuration ourConf;
294             if (cfgSpec != null && !cfgSpec.trim().equals("|")) {
295               cfgSpec = cfgSpec.substring(cfgSpec.indexOf('|') + 1);
296               // do an explicit deep copy of the passed configuration
297               ourConf = new Configuration(false);
298               HBaseConfiguration.merge(ourConf, conf);
299               Matcher m = HConstants.CP_HTD_ATTR_VALUE_PARAM_PATTERN.matcher(cfgSpec);
300               while (m.find()) {
301                 ourConf.set(m.group(1), m.group(2));
302               }
303             } else {
304               ourConf = conf;
305             }
306             result.add(new TableCoprocessorAttribute(path, className, priority, ourConf));
307           } else {
308             LOG.error("Malformed table coprocessor specification: key=" + key +
309               ", spec: " + spec);
310           }
311         } catch (Exception ioe) {
312           LOG.error("Malformed table coprocessor specification: key=" + key +
313             ", spec: " + spec);
314         }
315       }
316     }
317     return result;
318   }
319 
320   /**
321    * Sanity check the table coprocessor attributes of the supplied schema. Will
322    * throw an exception if there is a problem.
323    * @param conf
324    * @param htd
325    * @throws IOException
326    */
327   public static void testTableCoprocessorAttrs(final Configuration conf,
328       final HTableDescriptor htd) throws IOException {
329     String pathPrefix = UUID.randomUUID().toString();
330     for (TableCoprocessorAttribute attr: getTableCoprocessorAttrsFromSchema(conf, htd)) {
331       if (attr.getPriority() < 0) {
332         throw new IOException("Priority for coprocessor " + attr.getClassName() +
333           " cannot be less than 0");
334       }
335       ClassLoader old = Thread.currentThread().getContextClassLoader();
336       try {
337         ClassLoader cl;
338         if (attr.getPath() != null) {
339           cl = CoprocessorClassLoader.getClassLoader(attr.getPath(),
340             CoprocessorHost.class.getClassLoader(), pathPrefix, conf);
341         } else {
342           cl = CoprocessorHost.class.getClassLoader();
343         }
344         Thread.currentThread().setContextClassLoader(cl);
345         cl.loadClass(attr.getClassName());
346       } catch (ClassNotFoundException e) {
347         throw new IOException("Class " + attr.getClassName() + " cannot be loaded", e);
348       } finally {
349         Thread.currentThread().setContextClassLoader(old);
350       }
351     }
352   }
353 
354   void loadTableCoprocessors(final Configuration conf) {
355     boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY,
356       DEFAULT_COPROCESSORS_ENABLED);
357     boolean tableCoprocessorsEnabled = conf.getBoolean(USER_COPROCESSORS_ENABLED_CONF_KEY,
358       DEFAULT_USER_COPROCESSORS_ENABLED);
359     if (!(coprocessorsEnabled && tableCoprocessorsEnabled)) {
360       return;
361     }
362 
363     // scan the table attributes for coprocessor load specifications
364     // initialize the coprocessors
365     List<RegionEnvironment> configured = new ArrayList<RegionEnvironment>();
366     for (TableCoprocessorAttribute attr: getTableCoprocessorAttrsFromSchema(conf, 
367         region.getTableDesc())) {
368       // Load encompasses classloading and coprocessor initialization
369       try {
370         RegionEnvironment env = load(attr.getPath(), attr.getClassName(), attr.getPriority(),
371           attr.getConf());
372         configured.add(env);
373         LOG.info("Loaded coprocessor " + attr.getClassName() + " from HTD of " +
374             region.getTableDesc().getTableName().getNameAsString() + " successfully.");
375       } catch (Throwable t) {
376         // Coprocessor failed to load, do we abort on error?
377         if (conf.getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
378           abortServer(attr.getClassName(), t);
379         } else {
380           LOG.error("Failed to load coprocessor " + attr.getClassName(), t);
381         }
382       }
383     }
384     // add together to coprocessor set for COW efficiency
385     coprocessors.addAll(configured);
386   }
387 
388   @Override
389   public RegionEnvironment createEnvironment(Class<?> implClass,
390       Coprocessor instance, int priority, int seq, Configuration conf) {
391     // Check if it's an Endpoint.
392     // Due to current dynamic protocol design, Endpoint
393     // uses a different way to be registered and executed.
394     // It uses a visitor pattern to invoke registered Endpoint
395     // method.
396     for (Object itf : ClassUtils.getAllInterfaces(implClass)) {
397       Class<?> c = (Class<?>) itf;
398       if (CoprocessorService.class.isAssignableFrom(c)) {
399         region.registerService( ((CoprocessorService)instance).getService() );
400       }
401     }
402     ConcurrentMap<String, Object> classData;
403     // make sure only one thread can add maps
404     synchronized (sharedDataMap) {
405       // as long as at least one RegionEnvironment holds on to its classData it will
406       // remain in this map
407       classData = (ConcurrentMap<String, Object>)sharedDataMap.get(implClass.getName());
408       if (classData == null) {
409         classData = new ConcurrentHashMap<String, Object>();
410         sharedDataMap.put(implClass.getName(), classData);
411       }
412     }
413     return new RegionEnvironment(instance, priority, seq, conf, region,
414         rsServices, classData);
415   }
416 
417   /**
418    * HBASE-4014 : This is used by coprocessor hooks which are not declared to throw exceptions.
419    *
420    * For example, {@link
421    * org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost#preOpen()} and
422    * {@link org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost#postOpen()} are such hooks.
423    *
424    * See also
425    * {@link org.apache.hadoop.hbase.master.MasterCoprocessorHost#handleCoprocessorThrowable(
426    *    CoprocessorEnvironment, Throwable)}
427    * @param env The coprocessor that threw the exception.
428    * @param e The exception that was thrown.
429    */
430   private void handleCoprocessorThrowableNoRethrow(
431       final CoprocessorEnvironment env, final Throwable e) {
432     try {
433       handleCoprocessorThrowable(env,e);
434     } catch (IOException ioe) {
435       // We cannot throw exceptions from the caller hook, so ignore.
436       LOG.warn(
437         "handleCoprocessorThrowable() threw an IOException while attempting to handle Throwable " +
438         e + ". Ignoring.",e);
439     }
440   }
441 
442   /**
443    * Invoked before a region open.
444    *
445    * @throws IOException Signals that an I/O exception has occurred.
446    */
447   public void preOpen() throws IOException {
448     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
449       @Override
450       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
451           throws IOException {
452         oserver.preOpen(ctx);
453       }
454     });
455   }
456 
457   /**
458    * Invoked after a region open
459    */
460   public void postOpen() {
461     try {
462       execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
463         @Override
464         public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
465             throws IOException {
466           oserver.postOpen(ctx);
467         }
468       });
469     } catch (IOException e) {
470       LOG.warn(e);
471     }
472   }
473 
474   /**
475    * Invoked after log replay on region
476    */
477   public void postLogReplay() {
478     try {
479       execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
480         @Override
481         public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
482             throws IOException {
483           oserver.postLogReplay(ctx);
484         }
485       });
486     } catch (IOException e) {
487       LOG.warn(e);
488     }
489   }
490 
491   /**
492    * Invoked before a region is closed
493    * @param abortRequested true if the server is aborting
494    */
495   public void preClose(final boolean abortRequested) throws IOException {
496     execOperation(false, new RegionOperation() {
497       @Override
498       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
499           throws IOException {
500         oserver.preClose(ctx, abortRequested);
501       }
502     });
503   }
504 
505   /**
506    * Invoked after a region is closed
507    * @param abortRequested true if the server is aborting
508    */
509   public void postClose(final boolean abortRequested) {
510     try {
511       execOperation(false, new RegionOperation() {
512         @Override
513         public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
514             throws IOException {
515           oserver.postClose(ctx, abortRequested);
516         }
517         public void postEnvCall(RegionEnvironment env) {
518           shutdown(env);
519         }
520       });
521     } catch (IOException e) {
522       LOG.warn(e);
523     }
524   }
525 
526   /**
527    * See
528    * {@link RegionObserver#preCompactScannerOpen(ObserverContext, Store, List, ScanType, long, InternalScanner, CompactionRequest)}
529    */
530   public InternalScanner preCompactScannerOpen(final Store store,
531       final List<StoreFileScanner> scanners, final ScanType scanType, final long earliestPutTs,
532       final CompactionRequest request) throws IOException {
533     return execOperationWithResult(null,
534         coprocessors.isEmpty() ? null : new RegionOperationWithResult<InternalScanner>() {
535       @Override
536       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
537           throws IOException {
538         setResult(oserver.preCompactScannerOpen(ctx, store, scanners, scanType,
539           earliestPutTs, getResult(), request));
540       }
541     });
542   }
543 
544   /**
545    * Called prior to selecting the {@link StoreFile}s for compaction from the list of currently
546    * available candidates.
547    * @param store The store where compaction is being requested
548    * @param candidates The currently available store files
549    * @param request custom compaction request
550    * @return If {@code true}, skip the normal selection process and use the current list
551    * @throws IOException
552    */
553   public boolean preCompactSelection(final Store store, final List<StoreFile> candidates,
554       final CompactionRequest request) throws IOException {
555     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
556       @Override
557       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
558           throws IOException {
559         oserver.preCompactSelection(ctx, store, candidates, request);
560       }
561     });
562   }
563 
564   /**
565    * Called after the {@link StoreFile}s to be compacted have been selected from the available
566    * candidates.
567    * @param store The store where compaction is being requested
568    * @param selected The store files selected to compact
569    * @param request custom compaction
570    */
571   public void postCompactSelection(final Store store, final ImmutableList<StoreFile> selected,
572       final CompactionRequest request) {
573     try {
574       execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
575         @Override
576         public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
577             throws IOException {
578           oserver.postCompactSelection(ctx, store, selected, request);
579         }
580       });
581     } catch (IOException e) {
582       LOG.warn(e);
583     }
584   }
585 
586   /**
587    * Called prior to rewriting the store files selected for compaction
588    * @param store the store being compacted
589    * @param scanner the scanner used to read store data during compaction
590    * @param scanType type of Scan
591    * @param request the compaction that will be executed
592    * @throws IOException
593    */
594   public InternalScanner preCompact(final Store store, final InternalScanner scanner,
595       final ScanType scanType, final CompactionRequest request) throws IOException {
596     return execOperationWithResult(false, scanner,
597         coprocessors.isEmpty() ? null : new RegionOperationWithResult<InternalScanner>() {
598       @Override
599       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
600           throws IOException {
601         setResult(oserver.preCompact(ctx, store, getResult(), scanType, request));
602       }
603     });
604   }
605 
606   /**
607    * Called after the store compaction has completed.
608    * @param store the store being compacted
609    * @param resultFile the new store file written during compaction
610    * @param request the compaction that is being executed
611    * @throws IOException
612    */
613   public void postCompact(final Store store, final StoreFile resultFile,
614       final CompactionRequest request) throws IOException {
615     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
616       @Override
617       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
618           throws IOException {
619         oserver.postCompact(ctx, store, resultFile, request);
620       }
621     });
622   }
623 
624   /**
625    * Invoked before a memstore flush
626    * @throws IOException
627    */
628   public InternalScanner preFlush(final Store store, final InternalScanner scanner)
629       throws IOException {
630     return execOperationWithResult(false, scanner,
631         coprocessors.isEmpty() ? null : new RegionOperationWithResult<InternalScanner>() {
632       @Override
633       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
634           throws IOException {
635         setResult(oserver.preFlush(ctx, store, getResult()));
636       }
637     });
638   }
639 
640   /**
641    * Invoked before a memstore flush
642    * @throws IOException
643    */
644   public void preFlush() throws IOException {
645     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
646       @Override
647       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
648           throws IOException {
649         oserver.preFlush(ctx);
650       }
651     });
652   }
653 
654   /**
655    * See
656    * {@link RegionObserver#preFlushScannerOpen(ObserverContext,
657    *    Store, KeyValueScanner, InternalScanner)}
658    */
659   public InternalScanner preFlushScannerOpen(final Store store,
660       final KeyValueScanner memstoreScanner) throws IOException {
661     return execOperationWithResult(null,
662         coprocessors.isEmpty() ? null : new RegionOperationWithResult<InternalScanner>() {
663       @Override
664       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
665           throws IOException {
666         setResult(oserver.preFlushScannerOpen(ctx, store, memstoreScanner, getResult()));
667       }
668     });
669   }
670 
671   /**
672    * Invoked after a memstore flush
673    * @throws IOException
674    */
675   public void postFlush() throws IOException {
676     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
677       @Override
678       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
679           throws IOException {
680         oserver.postFlush(ctx);
681       }
682     });
683   }
684 
685   /**
686    * Invoked after a memstore flush
687    * @throws IOException
688    */
689   public void postFlush(final Store store, final StoreFile storeFile) throws IOException {
690     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
691       @Override
692       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
693           throws IOException {
694         oserver.postFlush(ctx, store, storeFile);
695       }
696     });
697   }
698 
699   /**
700    * Invoked just before a split
701    * @throws IOException
702    */
703   // TODO: Deprecate this
704   public void preSplit() throws IOException {
705     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
706       @Override
707       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
708           throws IOException {
709         oserver.preSplit(ctx);
710       }
711     });
712   }
713 
714   /**
715    * Invoked just before a split
716    * @throws IOException
717    */
718   public void preSplit(final byte[] splitRow) throws IOException {
719     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
720       @Override
721       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
722           throws IOException {
723         oserver.preSplit(ctx, splitRow);
724       }
725     });
726   }
727 
728   /**
729    * Invoked just after a split
730    * @param l the new left-hand daughter region
731    * @param r the new right-hand daughter region
732    * @throws IOException
733    */
734   public void postSplit(final HRegion l, final HRegion r) throws IOException {
735     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
736       @Override
737       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
738           throws IOException {
739         oserver.postSplit(ctx, l, r);
740       }
741     });
742   }
743 
744   public boolean preSplitBeforePONR(final byte[] splitKey,
745       final List<Mutation> metaEntries) throws IOException {
746     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
747       @Override
748       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
749           throws IOException {
750         oserver.preSplitBeforePONR(ctx, splitKey, metaEntries);
751       }
752     });
753   }
754 
755   public void preSplitAfterPONR() throws IOException {
756     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
757       @Override
758       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
759           throws IOException {
760         oserver.preSplitAfterPONR(ctx);
761       }
762     });
763   }
764 
765   /**
766    * Invoked just before the rollback of a failed split is started
767    * @throws IOException
768    */
769   public void preRollBackSplit() throws IOException {
770     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
771       @Override
772       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
773           throws IOException {
774         oserver.preRollBackSplit(ctx);
775       }
776     });
777   }
778 
779   /**
780    * Invoked just after the rollback of a failed split is done
781    * @throws IOException
782    */
783   public void postRollBackSplit() throws IOException {
784     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
785       @Override
786       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
787           throws IOException {
788         oserver.postRollBackSplit(ctx);
789       }
790     });
791   }
792 
793   /**
794    * Invoked after a split is completed irrespective of a failure or success.
795    * @throws IOException
796    */
797   public void postCompleteSplit() throws IOException {
798     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
799       @Override
800       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
801           throws IOException {
802         oserver.postCompleteSplit(ctx);
803       }
804     });
805   }
806 
807   // RegionObserver support
808 
809   /**
810    * @param row the row key
811    * @param family the family
812    * @param result the result set from the region
813    * @return true if default processing should be bypassed
814    * @exception IOException Exception
815    */
816   public boolean preGetClosestRowBefore(final byte[] row, final byte[] family,
817       final Result result) throws IOException {
818     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
819       @Override
820       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
821           throws IOException {
822         oserver.preGetClosestRowBefore(ctx, row, family, result);
823       }
824     });
825   }
826 
827   /**
828    * @param row the row key
829    * @param family the family
830    * @param result the result set from the region
831    * @exception IOException Exception
832    */
833   public void postGetClosestRowBefore(final byte[] row, final byte[] family,
834       final Result result) throws IOException {
835     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
836       @Override
837       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
838           throws IOException {
839         oserver.postGetClosestRowBefore(ctx, row, family, result);
840       }
841     });
842   }
843 
844   /**
845    * @param get the Get request
846    * @return true if default processing should be bypassed
847    * @exception IOException Exception
848    */
849   public boolean preGet(final Get get, final List<Cell> results)
850       throws IOException {
851     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
852       @Override
853       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
854           throws IOException {
855         oserver.preGetOp(ctx, get, results);
856       }
857     });
858   }
859 
860   /**
861    * @param get the Get request
862    * @param results the result sett
863    * @exception IOException Exception
864    */
865   public void postGet(final Get get, final List<Cell> results)
866       throws IOException {
867     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
868       @Override
869       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
870           throws IOException {
871         oserver.postGetOp(ctx, get, results);
872       }
873     });
874   }
875 
876   /**
877    * @param get the Get request
878    * @return true or false to return to client if bypassing normal operation,
879    * or null otherwise
880    * @exception IOException Exception
881    */
882   public Boolean preExists(final Get get) throws IOException {
883     return execOperationWithResult(true, false,
884         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
885       @Override
886       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
887           throws IOException {
888         setResult(oserver.preExists(ctx, get, getResult()));
889       }
890     });
891   }
892 
893   /**
894    * @param get the Get request
895    * @param exists the result returned by the region server
896    * @return the result to return to the client
897    * @exception IOException Exception
898    */
899   public boolean postExists(final Get get, boolean exists)
900       throws IOException {
901     return execOperationWithResult(exists,
902         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
903       @Override
904       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
905           throws IOException {
906         setResult(oserver.postExists(ctx, get, getResult()));
907       }
908     });
909   }
910 
911   /**
912    * @param put The Put object
913    * @param edit The WALEdit object.
914    * @param durability The durability used
915    * @return true if default processing should be bypassed
916    * @exception IOException Exception
917    */
918   public boolean prePut(final Put put, final WALEdit edit, final Durability durability)
919       throws IOException {
920     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
921       @Override
922       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
923           throws IOException {
924         oserver.prePut(ctx, put, edit, durability);
925       }
926     });
927   }
928 
929   /**
930    * @param mutation - the current mutation
931    * @param kv - the current cell
932    * @param byteNow - current timestamp in bytes
933    * @param get - the get that could be used
934    * Note that the get only does not specify the family and qualifier that should be used
935    * @return true if default processing should be bypassed
936    * @exception IOException
937    *              Exception
938    */
939   public boolean prePrepareTimeStampForDeleteVersion(final Mutation mutation,
940       final Cell kv, final byte[] byteNow, final Get get) throws IOException {
941     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
942       @Override
943       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
944           throws IOException {
945         oserver.prePrepareTimeStampForDeleteVersion(ctx, mutation, kv, byteNow, get);
946       }
947     });
948   }
949 
950   /**
951    * @param put The Put object
952    * @param edit The WALEdit object.
953    * @param durability The durability used
954    * @exception IOException Exception
955    */
956   public void postPut(final Put put, final WALEdit edit, final Durability durability)
957       throws IOException {
958     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
959       @Override
960       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
961           throws IOException {
962         oserver.postPut(ctx, put, edit, durability);
963       }
964     });
965   }
966 
967   /**
968    * @param delete The Delete object
969    * @param edit The WALEdit object.
970    * @param durability The durability used
971    * @return true if default processing should be bypassed
972    * @exception IOException Exception
973    */
974   public boolean preDelete(final Delete delete, final WALEdit edit, final Durability durability)
975       throws IOException {
976     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
977       @Override
978       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
979           throws IOException {
980         oserver.preDelete(ctx, delete, edit, durability);
981       }
982     });
983   }
984 
985   /**
986    * @param delete The Delete object
987    * @param edit The WALEdit object.
988    * @param durability The durability used
989    * @exception IOException Exception
990    */
991   public void postDelete(final Delete delete, final WALEdit edit, final Durability durability)
992       throws IOException {
993     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
994       @Override
995       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
996           throws IOException {
997         oserver.postDelete(ctx, delete, edit, durability);
998       }
999     });
1000   }
1001 
1002   /**
1003    * @param miniBatchOp
1004    * @return true if default processing should be bypassed
1005    * @throws IOException
1006    */
1007   public boolean preBatchMutate(
1008       final MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1009     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1010       @Override
1011       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1012           throws IOException {
1013         oserver.preBatchMutate(ctx, miniBatchOp);
1014       }
1015     });
1016   }
1017 
1018   /**
1019    * @param miniBatchOp
1020    * @throws IOException
1021    */
1022   public void postBatchMutate(
1023       final MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1024     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1025       @Override
1026       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1027           throws IOException {
1028         oserver.postBatchMutate(ctx, miniBatchOp);
1029       }
1030     });
1031   }
1032 
1033   public void postBatchMutateIndispensably(
1034       final MiniBatchOperationInProgress<Mutation> miniBatchOp, final boolean success)
1035       throws IOException {
1036     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1037       @Override
1038       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1039           throws IOException {
1040         oserver.postBatchMutateIndispensably(ctx, miniBatchOp, success);
1041       }
1042     });
1043   }
1044 
1045   /**
1046    * @param row row to check
1047    * @param family column family
1048    * @param qualifier column qualifier
1049    * @param compareOp the comparison operation
1050    * @param comparator the comparator
1051    * @param put data to put if check succeeds
1052    * @return true or false to return to client if default processing should
1053    * be bypassed, or null otherwise
1054    * @throws IOException e
1055    */
1056   public Boolean preCheckAndPut(final byte [] row, final byte [] family,
1057       final byte [] qualifier, final CompareOp compareOp,
1058       final ByteArrayComparable comparator, final Put put)
1059       throws IOException {
1060     return execOperationWithResult(true, false,
1061         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1062       @Override
1063       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1064           throws IOException {
1065         setResult(oserver.preCheckAndPut(ctx, row, family, qualifier,
1066           compareOp, comparator, put, getResult()));
1067       }
1068     });
1069   }
1070 
1071   /**
1072    * @param row row to check
1073    * @param family column family
1074    * @param qualifier column qualifier
1075    * @param compareOp the comparison operation
1076    * @param comparator the comparator
1077    * @param put data to put if check succeeds
1078    * @return true or false to return to client if default processing should
1079    * be bypassed, or null otherwise
1080    * @throws IOException e
1081    */
1082   public Boolean preCheckAndPutAfterRowLock(final byte[] row, final byte[] family,
1083       final byte[] qualifier, final CompareOp compareOp, final ByteArrayComparable comparator,
1084       final Put put) throws IOException {
1085     return execOperationWithResult(true, false,
1086         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1087       @Override
1088       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1089           throws IOException {
1090         setResult(oserver.preCheckAndPutAfterRowLock(ctx, row, family, qualifier,
1091           compareOp, comparator, put, getResult()));
1092       }
1093     });
1094   }
1095 
1096   /**
1097    * @param row row to check
1098    * @param family column family
1099    * @param qualifier column qualifier
1100    * @param compareOp the comparison operation
1101    * @param comparator the comparator
1102    * @param put data to put if check succeeds
1103    * @throws IOException e
1104    */
1105   public boolean postCheckAndPut(final byte [] row, final byte [] family,
1106       final byte [] qualifier, final CompareOp compareOp,
1107       final ByteArrayComparable comparator, final Put put,
1108       boolean result) throws IOException {
1109     return execOperationWithResult(result,
1110         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1111       @Override
1112       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1113           throws IOException {
1114         setResult(oserver.postCheckAndPut(ctx, row, family, qualifier,
1115           compareOp, comparator, put, getResult()));
1116       }
1117     });
1118   }
1119 
1120   /**
1121    * @param row row to check
1122    * @param family column family
1123    * @param qualifier column qualifier
1124    * @param compareOp the comparison operation
1125    * @param comparator the comparator
1126    * @param delete delete to commit if check succeeds
1127    * @return true or false to return to client if default processing should
1128    * be bypassed, or null otherwise
1129    * @throws IOException e
1130    */
1131   public Boolean preCheckAndDelete(final byte [] row, final byte [] family,
1132       final byte [] qualifier, final CompareOp compareOp,
1133       final ByteArrayComparable comparator, final Delete delete)
1134       throws IOException {
1135     return execOperationWithResult(true, false,
1136         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1137       @Override
1138       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1139           throws IOException {
1140         setResult(oserver.preCheckAndDelete(ctx, row, family,
1141             qualifier, compareOp, comparator, delete, getResult()));
1142       }
1143     });
1144   }
1145 
1146   /**
1147    * @param row row to check
1148    * @param family column family
1149    * @param qualifier column qualifier
1150    * @param compareOp the comparison operation
1151    * @param comparator the comparator
1152    * @param delete delete to commit if check succeeds
1153    * @return true or false to return to client if default processing should
1154    * be bypassed, or null otherwise
1155    * @throws IOException e
1156    */
1157   public Boolean preCheckAndDeleteAfterRowLock(final byte[] row, final byte[] family,
1158       final byte[] qualifier, final CompareOp compareOp, final ByteArrayComparable comparator,
1159       final Delete delete) throws IOException {
1160     return execOperationWithResult(true, false,
1161         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1162       @Override
1163       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1164           throws IOException {
1165         setResult(oserver.preCheckAndDeleteAfterRowLock(ctx, row,
1166               family, qualifier, compareOp, comparator, delete, getResult()));
1167       }
1168     });
1169   }
1170 
1171   /**
1172    * @param row row to check
1173    * @param family column family
1174    * @param qualifier column qualifier
1175    * @param compareOp the comparison operation
1176    * @param comparator the comparator
1177    * @param delete delete to commit if check succeeds
1178    * @throws IOException e
1179    */
1180   public boolean postCheckAndDelete(final byte [] row, final byte [] family,
1181       final byte [] qualifier, final CompareOp compareOp,
1182       final ByteArrayComparable comparator, final Delete delete,
1183       boolean result) throws IOException {
1184     return execOperationWithResult(result,
1185         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1186       @Override
1187       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1188           throws IOException {
1189         setResult(oserver.postCheckAndDelete(ctx, row, family,
1190             qualifier, compareOp, comparator, delete, getResult()));
1191       }
1192     });
1193   }
1194 
1195   /**
1196    * @param append append object
1197    * @return result to return to client if default operation should be
1198    * bypassed, null otherwise
1199    * @throws IOException if an error occurred on the coprocessor
1200    */
1201   public Result preAppend(final Append append) throws IOException {
1202     return execOperationWithResult(true, null,
1203         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Result>() {
1204       @Override
1205       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1206           throws IOException {
1207         setResult(oserver.preAppend(ctx, append));
1208       }
1209     });
1210   }
1211 
1212   /**
1213    * @param append append object
1214    * @return result to return to client if default operation should be
1215    * bypassed, null otherwise
1216    * @throws IOException if an error occurred on the coprocessor
1217    */
1218   public Result preAppendAfterRowLock(final Append append) throws IOException {
1219     return execOperationWithResult(true, null,
1220         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Result>() {
1221       @Override
1222       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1223           throws IOException {
1224         setResult(oserver.preAppendAfterRowLock(ctx, append));
1225       }
1226     });
1227   }
1228 
1229   /**
1230    * @param increment increment object
1231    * @return result to return to client if default operation should be
1232    * bypassed, null otherwise
1233    * @throws IOException if an error occurred on the coprocessor
1234    */
1235   public Result preIncrement(final Increment increment) throws IOException {
1236     return execOperationWithResult(true, null,
1237         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Result>() {
1238       @Override
1239       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1240           throws IOException {
1241         setResult(oserver.preIncrement(ctx, increment));
1242       }
1243     });
1244   }
1245 
1246   /**
1247    * @param increment increment object
1248    * @return result to return to client if default operation should be
1249    * bypassed, null otherwise
1250    * @throws IOException if an error occurred on the coprocessor
1251    */
1252   public Result preIncrementAfterRowLock(final Increment increment) throws IOException {
1253     return execOperationWithResult(true, null,
1254         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Result>() {
1255       @Override
1256       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1257           throws IOException {
1258         setResult(oserver.preIncrementAfterRowLock(ctx, increment));
1259       }
1260     });
1261   }
1262 
1263   /**
1264    * @param append Append object
1265    * @param result the result returned by the append
1266    * @throws IOException if an error occurred on the coprocessor
1267    */
1268   public void postAppend(final Append append, final Result result) throws IOException {
1269     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1270       @Override
1271       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1272           throws IOException {
1273         oserver.postAppend(ctx, append, result);
1274       }
1275     });
1276   }
1277 
1278   /**
1279    * @param increment increment object
1280    * @param result the result returned by postIncrement
1281    * @throws IOException if an error occurred on the coprocessor
1282    */
1283   public Result postIncrement(final Increment increment, Result result) throws IOException {
1284     return execOperationWithResult(result,
1285         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Result>() {
1286       @Override
1287       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1288           throws IOException {
1289         setResult(oserver.postIncrement(ctx, increment, getResult()));
1290       }
1291     });
1292   }
1293 
1294   /**
1295    * @param scan the Scan specification
1296    * @return scanner id to return to client if default operation should be
1297    * bypassed, false otherwise
1298    * @exception IOException Exception
1299    */
1300   public RegionScanner preScannerOpen(final Scan scan) throws IOException {
1301     return execOperationWithResult(true, null,
1302         coprocessors.isEmpty() ? null : new RegionOperationWithResult<RegionScanner>() {
1303       @Override
1304       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1305           throws IOException {
1306         setResult(oserver.preScannerOpen(ctx, scan, getResult()));
1307       }
1308     });
1309   }
1310 
1311   /**
1312    * See
1313    * {@link RegionObserver#preStoreScannerOpen(ObserverContext,
1314    *    Store, Scan, NavigableSet, KeyValueScanner)}
1315    */
1316   public KeyValueScanner preStoreScannerOpen(final Store store, final Scan scan,
1317       final NavigableSet<byte[]> targetCols) throws IOException {
1318     return execOperationWithResult(null,
1319         coprocessors.isEmpty() ? null : new RegionOperationWithResult<KeyValueScanner>() {
1320       @Override
1321       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1322           throws IOException {
1323         setResult(oserver.preStoreScannerOpen(ctx, store, scan, targetCols, getResult()));
1324       }
1325     });
1326   }
1327 
1328   /**
1329    * @param scan the Scan specification
1330    * @param s the scanner
1331    * @return the scanner instance to use
1332    * @exception IOException Exception
1333    */
1334   public RegionScanner postScannerOpen(final Scan scan, RegionScanner s) throws IOException {
1335     return execOperationWithResult(s,
1336         coprocessors.isEmpty() ? null : new RegionOperationWithResult<RegionScanner>() {
1337       @Override
1338       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1339           throws IOException {
1340         setResult(oserver.postScannerOpen(ctx, scan, getResult()));
1341       }
1342     });
1343   }
1344 
1345   /**
1346    * @param s the scanner
1347    * @param results the result set returned by the region server
1348    * @param limit the maximum number of results to return
1349    * @return 'has next' indication to client if bypassing default behavior, or
1350    * null otherwise
1351    * @exception IOException Exception
1352    */
1353   public Boolean preScannerNext(final InternalScanner s,
1354       final List<Result> results, final int limit) throws IOException {
1355     return execOperationWithResult(true, false,
1356         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1357       @Override
1358       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1359           throws IOException {
1360         setResult(oserver.preScannerNext(ctx, s, results, limit, getResult()));
1361       }
1362     });
1363   }
1364 
1365   /**
1366    * @param s the scanner
1367    * @param results the result set returned by the region server
1368    * @param limit the maximum number of results to return
1369    * @param hasMore
1370    * @return 'has more' indication to give to client
1371    * @exception IOException Exception
1372    */
1373   public boolean postScannerNext(final InternalScanner s,
1374       final List<Result> results, final int limit, boolean hasMore)
1375       throws IOException {
1376     return execOperationWithResult(hasMore,
1377         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1378       @Override
1379       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1380           throws IOException {
1381         setResult(oserver.postScannerNext(ctx, s, results, limit, getResult()));
1382       }
1383     });
1384   }
1385 
1386   /**
1387    * This will be called by the scan flow when the current scanned row is being filtered out by the
1388    * filter.
1389    * @param s the scanner
1390    * @param currentRow The current rowkey which got filtered out
1391    * @param offset offset to rowkey
1392    * @param length length of rowkey
1393    * @return whether more rows are available for the scanner or not
1394    * @throws IOException
1395    */
1396   public boolean postScannerFilterRow(final InternalScanner s, final byte[] currentRow,
1397       final int offset, final short length) throws IOException {
1398     // short circuit for performance
1399     if (!hasCustomPostScannerFilterRow) return true;
1400     return execOperationWithResult(true,
1401         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1402       @Override
1403       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1404           throws IOException {
1405         setResult(oserver.postScannerFilterRow(ctx, s, currentRow, offset,length, getResult()));
1406       }
1407     });
1408   }
1409 
1410   /**
1411    * @param s the scanner
1412    * @return true if default behavior should be bypassed, false otherwise
1413    * @exception IOException Exception
1414    */
1415   public boolean preScannerClose(final InternalScanner s) throws IOException {
1416     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1417       @Override
1418       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1419           throws IOException {
1420         oserver.preScannerClose(ctx, s);
1421       }
1422     });
1423   }
1424 
1425   /**
1426    * @exception IOException Exception
1427    */
1428   public void postScannerClose(final InternalScanner s) throws IOException {
1429     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1430       @Override
1431       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1432           throws IOException {
1433         oserver.postScannerClose(ctx, s);
1434       }
1435     });
1436   }
1437 
1438   /**
1439    * @param info
1440    * @param logKey
1441    * @param logEdit
1442    * @return true if default behavior should be bypassed, false otherwise
1443    * @throws IOException
1444    */
1445   public boolean preWALRestore(final HRegionInfo info, final HLogKey logKey,
1446       final WALEdit logEdit) throws IOException {
1447     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1448       @Override
1449       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1450           throws IOException {
1451         oserver.preWALRestore(ctx, info, logKey, logEdit);
1452       }
1453     });
1454   }
1455 
1456   /**
1457    * @param info
1458    * @param logKey
1459    * @param logEdit
1460    * @throws IOException
1461    */
1462   public void postWALRestore(final HRegionInfo info, final HLogKey logKey, final WALEdit logEdit)
1463       throws IOException {
1464     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1465       @Override
1466       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1467           throws IOException {
1468         oserver.postWALRestore(ctx, info, logKey, logEdit);
1469       }
1470     });
1471   }
1472 
1473   /**
1474    * @param familyPaths pairs of { CF, file path } submitted for bulk load
1475    * @return true if the default operation should be bypassed
1476    * @throws IOException
1477    */
1478   public boolean preBulkLoadHFile(final List<Pair<byte[], String>> familyPaths) throws IOException {
1479     return execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1480       @Override
1481       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1482           throws IOException {
1483         oserver.preBulkLoadHFile(ctx, familyPaths);
1484       }
1485     });
1486   }
1487 
1488   /**
1489    * @param familyPaths pairs of { CF, file path } submitted for bulk load
1490    * @param hasLoaded whether load was successful or not
1491    * @return the possibly modified value of hasLoaded
1492    * @throws IOException
1493    */
1494   public boolean postBulkLoadHFile(final List<Pair<byte[], String>> familyPaths,
1495       boolean hasLoaded) throws IOException {
1496     return execOperationWithResult(hasLoaded,
1497         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Boolean>() {
1498       @Override
1499       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1500           throws IOException {
1501         setResult(oserver.postBulkLoadHFile(ctx, familyPaths, getResult()));
1502       }
1503     });
1504   }
1505 
1506   public void postStartRegionOperation(final Operation op) throws IOException {
1507     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1508       @Override
1509       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1510           throws IOException {
1511         oserver.postStartRegionOperation(ctx, op);
1512       }
1513     });
1514   }
1515 
1516   public void postCloseRegionOperation(final Operation op) throws IOException {
1517     execOperation(coprocessors.isEmpty() ? null : new RegionOperation() {
1518       @Override
1519       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1520           throws IOException {
1521         oserver.postCloseRegionOperation(ctx, op);
1522       }
1523     });
1524   }
1525 
1526   /**
1527    * @param fs fileystem to read from
1528    * @param p path to the file
1529    * @param in {@link FSDataInputStreamWrapper}
1530    * @param size Full size of the file
1531    * @param cacheConf
1532    * @param r original reference file. This will be not null only when reading a split file.
1533    * @return a Reader instance to use instead of the base reader if overriding
1534    * default behavior, null otherwise
1535    * @throws IOException
1536    */
1537   public StoreFile.Reader preStoreFileReaderOpen(final FileSystem fs, final Path p,
1538       final FSDataInputStreamWrapper in, final long size, final CacheConfig cacheConf,
1539       final Reference r) throws IOException {
1540     return execOperationWithResult(null,
1541         coprocessors.isEmpty() ? null : new RegionOperationWithResult<StoreFile.Reader>() {
1542       @Override
1543       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1544           throws IOException {
1545         setResult(oserver.preStoreFileReaderOpen(ctx, fs, p, in, size, cacheConf, r, getResult()));
1546       }
1547     });
1548   }
1549 
1550   /**
1551    * @param fs fileystem to read from
1552    * @param p path to the file
1553    * @param in {@link FSDataInputStreamWrapper}
1554    * @param size Full size of the file
1555    * @param cacheConf
1556    * @param r original reference file. This will be not null only when reading a split file.
1557    * @param reader the base reader instance
1558    * @return The reader to use
1559    * @throws IOException
1560    */
1561   public StoreFile.Reader postStoreFileReaderOpen(final FileSystem fs, final Path p,
1562       final FSDataInputStreamWrapper in, final long size, final CacheConfig cacheConf,
1563       final Reference r, final StoreFile.Reader reader) throws IOException {
1564     return execOperationWithResult(reader,
1565         coprocessors.isEmpty() ? null : new RegionOperationWithResult<StoreFile.Reader>() {
1566       @Override
1567       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1568           throws IOException {
1569         setResult(oserver.postStoreFileReaderOpen(ctx, fs, p, in, size, cacheConf, r, getResult()));
1570       }
1571     });
1572   }
1573 
1574   public Cell postMutationBeforeWAL(final MutationType opType, final Mutation mutation,
1575       final Cell oldCell, Cell newCell) throws IOException {
1576     return execOperationWithResult(newCell,
1577         coprocessors.isEmpty() ? null : new RegionOperationWithResult<Cell>() {
1578       @Override
1579       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1580           throws IOException {
1581         setResult(oserver.postMutationBeforeWAL(ctx, opType, mutation, oldCell, getResult()));
1582       }
1583     });
1584   }
1585 
1586   public Message preEndpointInvocation(final Service service, final String methodName,
1587       Message request) throws IOException {
1588     return execOperationWithResult(request,
1589         coprocessors.isEmpty() ? null : new EndpointOperationWithResult<Message>() {
1590       @Override
1591       public void call(EndpointObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1592           throws IOException {
1593         setResult(oserver.preEndpointInvocation(ctx, service, methodName, getResult()));
1594       }
1595     });
1596   }
1597 
1598   public void postEndpointInvocation(final Service service, final String methodName,
1599       final Message request, final Message.Builder responseBuilder) throws IOException {
1600     execOperation(coprocessors.isEmpty() ? null : new EndpointOperation() {
1601       @Override
1602       public void call(EndpointObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1603           throws IOException {
1604         oserver.postEndpointInvocation(ctx, service, methodName, request, responseBuilder);
1605       }
1606     });
1607   }
1608 
1609   public DeleteTracker postInstantiateDeleteTracker(DeleteTracker tracker) throws IOException {
1610     return execOperationWithResult(tracker,
1611         coprocessors.isEmpty() ? null : new RegionOperationWithResult<DeleteTracker>() {
1612       @Override
1613       public void call(RegionObserver oserver, ObserverContext<RegionCoprocessorEnvironment> ctx)
1614           throws IOException {
1615         setResult(oserver.postInstantiateDeleteTracker(ctx, getResult()));
1616       }
1617     });
1618   }
1619 
1620   public Map<String, DescriptiveStatistics> getCoprocessorExecutionStatistics() {
1621     Map<String, DescriptiveStatistics> results = new HashMap<String, DescriptiveStatistics>();
1622     for (RegionEnvironment env : coprocessors) {
1623       DescriptiveStatistics ds = new DescriptiveStatistics();
1624       if (env.getInstance() instanceof RegionObserver) {
1625         for (Long time : env.getExecutionLatenciesNanos()) {
1626           ds.addValue(time);
1627         }
1628         // Ensures that web ui circumvents the display of NaN values when there are zero samples.
1629         if (ds.getN() == 0) {
1630           ds.addValue(0);
1631         }
1632         results.put(env.getInstance().getClass().getSimpleName(), ds);
1633       }
1634     }
1635     return results;
1636   }
1637 
1638   private static abstract class CoprocessorOperation
1639       extends ObserverContext<RegionCoprocessorEnvironment> {
1640     public abstract void call(Coprocessor observer,
1641         ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException;
1642     public abstract boolean hasCall(Coprocessor observer);
1643     public void postEnvCall(RegionEnvironment env) { }
1644   }
1645 
1646   private static abstract class RegionOperation extends CoprocessorOperation {
1647     public abstract void call(RegionObserver observer,
1648         ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException;
1649 
1650     public boolean hasCall(Coprocessor observer) {
1651       return observer instanceof RegionObserver;
1652     }
1653 
1654     public void call(Coprocessor observer, ObserverContext<RegionCoprocessorEnvironment> ctx)
1655         throws IOException {
1656       call((RegionObserver)observer, ctx);
1657     }
1658   }
1659 
1660   private static abstract class RegionOperationWithResult<T> extends RegionOperation {
1661     private T result = null;
1662     public void setResult(final T result) { this.result = result; }
1663     public T getResult() { return this.result; }
1664   }
1665 
1666   private static abstract class EndpointOperation extends CoprocessorOperation {
1667     public abstract void call(EndpointObserver observer,
1668         ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException;
1669 
1670     public boolean hasCall(Coprocessor observer) {
1671       return observer instanceof EndpointObserver;
1672     }
1673 
1674     public void call(Coprocessor observer, ObserverContext<RegionCoprocessorEnvironment> ctx)
1675         throws IOException {
1676       call((EndpointObserver)observer, ctx);
1677     }
1678   }
1679 
1680   private static abstract class EndpointOperationWithResult<T> extends EndpointOperation {
1681     private T result = null;
1682     public void setResult(final T result) { this.result = result; }
1683     public T getResult() { return this.result; }
1684   }
1685 
1686   private boolean execOperation(final CoprocessorOperation ctx)
1687       throws IOException {
1688     return execOperation(true, ctx);
1689   }
1690 
1691   private <T> T execOperationWithResult(final T defaultValue,
1692       final RegionOperationWithResult<T> ctx) throws IOException {
1693     if (ctx == null) return defaultValue;
1694     ctx.setResult(defaultValue);
1695     execOperation(true, ctx);
1696     return ctx.getResult();
1697   }
1698 
1699   private <T> T execOperationWithResult(final boolean ifBypass, final T defaultValue,
1700       final RegionOperationWithResult<T> ctx) throws IOException {
1701     boolean bypass = false;
1702     T result = defaultValue;
1703     if (ctx != null) {
1704       ctx.setResult(defaultValue);
1705       bypass = execOperation(true, ctx);
1706       result = ctx.getResult();
1707     }
1708     return bypass == ifBypass ? result : null;
1709   }
1710 
1711   private <T> T execOperationWithResult(final T defaultValue,
1712       final EndpointOperationWithResult<T> ctx) throws IOException {
1713     if (ctx == null) return defaultValue;
1714     ctx.setResult(defaultValue);
1715     execOperation(true, ctx);
1716     return ctx.getResult();
1717   }
1718 
1719   private boolean execOperation(final boolean earlyExit, final CoprocessorOperation ctx)
1720       throws IOException {
1721     boolean bypass = false;
1722     List<RegionEnvironment> envs = coprocessors.get();
1723     for (int i = 0; i < envs.size(); i++) {
1724       RegionEnvironment env = envs.get(i);
1725       Coprocessor observer = env.getInstance();
1726       if (ctx.hasCall(observer)) {
1727         long startTime = System.nanoTime();
1728         ctx.prepare(env);
1729         Thread currentThread = Thread.currentThread();
1730         ClassLoader cl = currentThread.getContextClassLoader();
1731         try {
1732           currentThread.setContextClassLoader(env.getClassLoader());
1733           ctx.call(observer, ctx);
1734         } catch (Throwable e) {
1735           handleCoprocessorThrowable(env, e);
1736         } finally {
1737           currentThread.setContextClassLoader(cl);
1738         }
1739         env.offerExecutionLatency(System.nanoTime() - startTime);
1740         bypass |= ctx.shouldBypass();
1741         if (earlyExit && ctx.shouldComplete()) {
1742           break;
1743         }
1744       }
1745 
1746       ctx.postEnvCall(env);
1747     }
1748     return bypass;
1749   }
1750 }