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