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