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