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