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.coprocessor;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.TreeSet;
30  import java.util.UUID;
31  import java.util.concurrent.ConcurrentSkipListSet;
32  import java.util.concurrent.ExecutorService;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
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.Path;
41  import org.apache.hadoop.hbase.Abortable;
42  import org.apache.hadoop.hbase.Coprocessor;
43  import org.apache.hadoop.hbase.CoprocessorEnvironment;
44  import org.apache.hadoop.hbase.DoNotRetryIOException;
45  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
46  import org.apache.hadoop.hbase.HConstants;
47  import org.apache.hadoop.hbase.TableName;
48  import org.apache.hadoop.hbase.client.HTable;
49  import org.apache.hadoop.hbase.client.HTableInterface;
50  import org.apache.hadoop.hbase.client.HTableWrapper;
51  import org.apache.hadoop.hbase.util.Bytes;
52  import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
53  import org.apache.hadoop.hbase.util.SortedList;
54  import org.apache.hadoop.hbase.util.VersionInfo;
55  
56  /**
57   * Provides the common setup framework and runtime services for coprocessor
58   * invocation from HBase services.
59   * @param <E> the specific environment extension that a concrete implementation
60   * provides
61   */
62  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
63  @InterfaceStability.Evolving
64  public abstract class CoprocessorHost<E extends CoprocessorEnvironment> {
65    public static final String REGION_COPROCESSOR_CONF_KEY =
66        "hbase.coprocessor.region.classes";
67    public static final String REGIONSERVER_COPROCESSOR_CONF_KEY =
68        "hbase.coprocessor.regionserver.classes";
69    public static final String USER_REGION_COPROCESSOR_CONF_KEY =
70        "hbase.coprocessor.user.region.classes";
71    public static final String MASTER_COPROCESSOR_CONF_KEY =
72        "hbase.coprocessor.master.classes";
73    public static final String WAL_COPROCESSOR_CONF_KEY =
74      "hbase.coprocessor.wal.classes";
75    public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror";
76    public static final boolean DEFAULT_ABORT_ON_ERROR = true;
77    public static final String COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.enabled";
78    public static final boolean DEFAULT_COPROCESSORS_ENABLED = true;
79    public static final String USER_COPROCESSORS_ENABLED_CONF_KEY =
80      "hbase.coprocessor.user.enabled";
81    public static final boolean DEFAULT_USER_COPROCESSORS_ENABLED = true;
82    public static final String SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR =
83        "hbase.skip.load.duplicate.table.coprocessor";
84    public static final boolean DEFAULT_SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR = false;
85  
86    private static final Log LOG = LogFactory.getLog(CoprocessorHost.class);
87    protected Abortable abortable;
88    /** Ordered set of loaded coprocessors with lock */
89    protected SortedList<E> coprocessors =
90        new SortedList<E>(new EnvironmentPriorityComparator());
91    protected Configuration conf;
92    // unique file prefix to use for local copies of jars when classloading
93    protected String pathPrefix;
94    protected AtomicInteger loadSequence = new AtomicInteger();
95  
96    public CoprocessorHost(Abortable abortable) {
97      this.abortable = abortable;
98      this.pathPrefix = UUID.randomUUID().toString();
99    }
100 
101   /**
102    * Not to be confused with the per-object _coprocessors_ (above),
103    * coprocessorNames is static and stores the set of all coprocessors ever
104    * loaded by any thread in this JVM. It is strictly additive: coprocessors are
105    * added to coprocessorNames, by loadInstance() but are never removed, since
106    * the intention is to preserve a history of all loaded coprocessors for
107    * diagnosis in case of server crash (HBASE-4014).
108    */
109   private static Set<String> coprocessorNames =
110       Collections.synchronizedSet(new HashSet<String>());
111 
112   public static Set<String> getLoadedCoprocessors() {
113     synchronized (coprocessorNames) {
114       return new HashSet(coprocessorNames);
115     }
116   }
117 
118   /**
119    * Used to create a parameter to the HServerLoad constructor so that
120    * HServerLoad can provide information about the coprocessors loaded by this
121    * regionserver.
122    * (HBASE-4070: Improve region server metrics to report loaded coprocessors
123    * to master).
124    */
125   public Set<String> getCoprocessors() {
126     Set<String> returnValue = new TreeSet<String>();
127     for (CoprocessorEnvironment e: coprocessors) {
128       returnValue.add(e.getInstance().getClass().getSimpleName());
129     }
130     return returnValue;
131   }
132 
133   /**
134    * Load system coprocessors once only. Read the class names from configuration.
135    * Called by constructor.
136    */
137   protected void loadSystemCoprocessors(Configuration conf, String confKey) {
138     boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY,
139       DEFAULT_COPROCESSORS_ENABLED);
140     if (!coprocessorsEnabled) {
141       return;
142     }
143 
144     Class<?> implClass = null;
145 
146     // load default coprocessors from configure file
147     String[] defaultCPClasses = conf.getStrings(confKey);
148     if (defaultCPClasses == null || defaultCPClasses.length == 0)
149       return;
150 
151     int currentSystemPriority = Coprocessor.PRIORITY_SYSTEM;
152     for (String className : defaultCPClasses) {
153       String[] classNameAndPriority = className.split("\\|");
154       boolean hasPriorityOverride = false;
155       className = classNameAndPriority[0];
156       int overridePriority = Coprocessor.PRIORITY_SYSTEM;
157       if (classNameAndPriority.length > 1){
158         overridePriority = Integer.parseInt(classNameAndPriority[1]);
159         hasPriorityOverride = true;
160       }
161       className = className.trim();
162       if (findCoprocessor(className) != null) {
163         // If already loaded will just continue
164         LOG.warn("Attempted duplicate loading of " + className + "; skipped");
165         continue;
166       }
167       ClassLoader cl = this.getClass().getClassLoader();
168       Thread.currentThread().setContextClassLoader(cl);
169       try {
170         implClass = cl.loadClass(className);
171         // Add coprocessors as we go to guard against case where a coprocessor is specified twice
172         // in the configuration
173         int coprocPriority = hasPriorityOverride ? overridePriority : currentSystemPriority;
174         this.coprocessors.add(loadInstance(implClass, coprocPriority, conf));
175         LOG.info("System coprocessor " + className + " was loaded " +
176             "successfully with priority (" + coprocPriority + ").");
177         if (!hasPriorityOverride) {
178           ++currentSystemPriority;
179         }
180       } catch (Throwable t) {
181         // We always abort if system coprocessors cannot be loaded
182         abortServer(className, t);
183       }
184     }
185   }
186 
187   /**
188    * Load a coprocessor implementation into the host
189    * @param path path to implementation jar
190    * @param className the main class name
191    * @param priority chaining priority
192    * @param conf configuration for coprocessor
193    * @throws java.io.IOException Exception
194    */
195   public E load(Path path, String className, int priority,
196       Configuration conf) throws IOException {
197     String[] includedClassPrefixes = null;
198     if (conf.get(HConstants.CP_HTD_ATTR_INCLUSION_KEY) != null){
199       String prefixes = conf.get(HConstants.CP_HTD_ATTR_INCLUSION_KEY);
200       includedClassPrefixes = prefixes.split(";");
201     }
202     return load(path, className, priority, conf, includedClassPrefixes);
203   }
204 
205   /**
206    * Load a coprocessor implementation into the host
207    * @param path path to implementation jar
208    * @param className the main class name
209    * @param priority chaining priority
210    * @param conf configuration for coprocessor
211    * @param includedClassPrefixes class name prefixes to include
212    * @throws java.io.IOException Exception
213    */
214   public E load(Path path, String className, int priority,
215       Configuration conf, String[] includedClassPrefixes) throws IOException {
216     Class<?> implClass = null;
217     LOG.debug("Loading coprocessor class " + className + " with path " +
218         path + " and priority " + priority);
219 
220     boolean skipLoadDuplicateCoprocessor = conf.getBoolean(SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR,
221       DEFAULT_SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR);
222     if (skipLoadDuplicateCoprocessor && findCoprocessor(className) != null) {
223       // If already loaded will just continue
224       LOG.warn("Attempted duplicate loading of " + className + "; skipped");
225       return null;
226     }
227 
228     ClassLoader cl = null;
229     if (path == null) {
230       try {
231         implClass = getClass().getClassLoader().loadClass(className);
232       } catch (ClassNotFoundException e) {
233         throw new IOException("No jar path specified for " + className);
234       }
235     } else {
236       cl = CoprocessorClassLoader.getClassLoader(
237         path, getClass().getClassLoader(), pathPrefix, conf);
238       try {
239         implClass = ((CoprocessorClassLoader)cl).loadClass(className, includedClassPrefixes);
240       } catch (ClassNotFoundException e) {
241         throw new IOException("Cannot load external coprocessor class " + className, e);
242       }
243     }
244 
245     //load custom code for coprocessor
246     Thread currentThread = Thread.currentThread();
247     ClassLoader hostClassLoader = currentThread.getContextClassLoader();
248     try{
249       // switch temporarily to the thread classloader for custom CP
250       currentThread.setContextClassLoader(cl);
251       E cpInstance = loadInstance(implClass, priority, conf);
252       return cpInstance;
253     } finally {
254       // restore the fresh (host) classloader
255       currentThread.setContextClassLoader(hostClassLoader);
256     }
257   }
258 
259   /**
260    * @param implClass Implementation class
261    * @param priority priority
262    * @param conf configuration
263    * @throws java.io.IOException Exception
264    */
265   public void load(Class<?> implClass, int priority, Configuration conf)
266       throws IOException {
267     E env = loadInstance(implClass, priority, conf);
268     coprocessors.add(env);
269   }
270 
271   /**
272    * @param implClass Implementation class
273    * @param priority priority
274    * @param conf configuration
275    * @throws java.io.IOException Exception
276    */
277   public E loadInstance(Class<?> implClass, int priority, Configuration conf)
278       throws IOException {
279     if (!Coprocessor.class.isAssignableFrom(implClass)) {
280       throw new IOException("Configured class " + implClass.getName() + " must implement "
281           + Coprocessor.class.getName() + " interface ");
282     }
283 
284     // create the instance
285     Coprocessor impl;
286     Object o = null;
287     try {
288       o = implClass.getDeclaredConstructor().newInstance();
289       impl = (Coprocessor)o;
290     } catch (Exception e) {
291       throw new IOException(e);
292     }
293     // create the environment
294     E env = createEnvironment(implClass, impl, priority, loadSequence.incrementAndGet(), conf);
295     if (env instanceof Environment) {
296       ((Environment)env).startup();
297     }
298     // HBASE-4014: maintain list of loaded coprocessors for later crash analysis
299     // if server (master or regionserver) aborts.
300     coprocessorNames.add(implClass.getName());
301     return env;
302   }
303 
304   /**
305    * Called when a new Coprocessor class is loaded
306    */
307   public abstract E createEnvironment(Class<?> implClass, Coprocessor instance,
308       int priority, int sequence, Configuration conf);
309 
310   public void shutdown(CoprocessorEnvironment e) {
311     if (e instanceof Environment) {
312       if (LOG.isDebugEnabled()) {
313         LOG.debug("Stop coprocessor " + e.getInstance().getClass().getName());
314       }
315       ((Environment)e).shutdown();
316     } else {
317       LOG.warn("Shutdown called on unknown environment: "+
318           e.getClass().getName());
319     }
320   }
321 
322   /**
323    * Find a coprocessor implementation by class name
324    * @param className the class name
325    * @return the coprocessor, or null if not found
326    */
327   public Coprocessor findCoprocessor(String className) {
328     for (E env: coprocessors) {
329       if (env.getInstance().getClass().getName().equals(className) ||
330           env.getInstance().getClass().getSimpleName().equals(className)) {
331         return env.getInstance();
332       }
333     }
334     return null;
335   }
336 
337   /**
338    * Find list of coprocessors that extend/implement the given class/interface
339    * @param cls the class/interface to look for
340    * @return the list of coprocessors, or null if not found
341    */
342   public <T extends Coprocessor> List<T> findCoprocessors(Class<T> cls) {
343     ArrayList<T> ret = new ArrayList<T>();
344 
345     for (E env: coprocessors) {
346       Coprocessor cp = env.getInstance();
347 
348       if(cp != null) {
349         if (cls.isAssignableFrom(cp.getClass())) {
350           ret.add((T)cp);
351         }
352       }
353     }
354     return ret;
355   }
356 
357   /**
358    * Find a coprocessor environment by class name
359    * @param className the class name
360    * @return the coprocessor, or null if not found
361    */
362   public CoprocessorEnvironment findCoprocessorEnvironment(String className) {
363     for (E env: coprocessors) {
364       if (env.getInstance().getClass().getName().equals(className) ||
365           env.getInstance().getClass().getSimpleName().equals(className)) {
366         return env;
367       }
368     }
369     return null;
370   }
371 
372   /**
373    * Retrieves the set of classloaders used to instantiate Coprocessor classes defined in external
374    * jar files.
375    * @return A set of ClassLoader instances
376    */
377   Set<ClassLoader> getExternalClassLoaders() {
378     Set<ClassLoader> externalClassLoaders = new HashSet<ClassLoader>();
379     final ClassLoader systemClassLoader = this.getClass().getClassLoader();
380     for (E env : coprocessors) {
381       ClassLoader cl = env.getInstance().getClass().getClassLoader();
382       if (cl != systemClassLoader ){
383         //do not include system classloader
384         externalClassLoaders.add(cl);
385       }
386     }
387     return externalClassLoaders;
388   }
389 
390   /**
391    * Environment priority comparator.
392    * Coprocessors are chained in sorted order.
393    */
394   static class EnvironmentPriorityComparator
395       implements Comparator<CoprocessorEnvironment> {
396     @Override
397     public int compare(final CoprocessorEnvironment env1,
398         final CoprocessorEnvironment env2) {
399       if (env1.getPriority() < env2.getPriority()) {
400         return -1;
401       } else if (env1.getPriority() > env2.getPriority()) {
402         return 1;
403       }
404       if (env1.getLoadSequence() < env2.getLoadSequence()) {
405         return -1;
406       } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
407         return 1;
408       }
409       return 0;
410     }
411   }
412 
413   /**
414    * Encapsulation of the environment of each coprocessor
415    */
416   public static class Environment implements CoprocessorEnvironment {
417 
418     /** The coprocessor */
419     public Coprocessor impl;
420     /** Chaining priority */
421     protected int priority = Coprocessor.PRIORITY_USER;
422     /** Current coprocessor state */
423     Coprocessor.State state = Coprocessor.State.UNINSTALLED;
424     /** Accounting for tables opened by the coprocessor */
425     protected final List<HTableInterface> openTables =
426       Collections.synchronizedList(new ArrayList<HTableInterface>());
427     private int seq;
428     private Configuration conf;
429     private ClassLoader classLoader;
430 
431     /**
432      * Constructor
433      * @param impl the coprocessor instance
434      * @param priority chaining priority
435      */
436     public Environment(final Coprocessor impl, final int priority,
437         final int seq, final Configuration conf) {
438       this.impl = impl;
439       this.classLoader = impl.getClass().getClassLoader();
440       this.priority = priority;
441       this.state = Coprocessor.State.INSTALLED;
442       this.seq = seq;
443       this.conf = conf;
444     }
445 
446     /** Initialize the environment */
447     public void startup() throws IOException {
448       if (state == Coprocessor.State.INSTALLED ||
449           state == Coprocessor.State.STOPPED) {
450         state = Coprocessor.State.STARTING;
451         Thread currentThread = Thread.currentThread();
452         ClassLoader hostClassLoader = currentThread.getContextClassLoader();
453         try {
454           currentThread.setContextClassLoader(this.getClassLoader());
455           impl.start(this);
456           state = Coprocessor.State.ACTIVE;
457         } finally {
458           currentThread.setContextClassLoader(hostClassLoader);
459         }
460       } else {
461         LOG.warn("Not starting coprocessor "+impl.getClass().getName()+
462             " because not inactive (state="+state.toString()+")");
463       }
464     }
465 
466     /** Clean up the environment */
467     protected void shutdown() {
468       if (state == Coprocessor.State.ACTIVE) {
469         state = Coprocessor.State.STOPPING;
470         Thread currentThread = Thread.currentThread();
471         ClassLoader hostClassLoader = currentThread.getContextClassLoader();
472         try {
473           currentThread.setContextClassLoader(this.getClassLoader());
474           impl.stop(this);
475           state = Coprocessor.State.STOPPED;
476         } catch (IOException ioe) {
477           LOG.error("Error stopping coprocessor "+impl.getClass().getName(), ioe);
478         } finally {
479           currentThread.setContextClassLoader(hostClassLoader);
480         }
481       } else {
482         LOG.warn("Not stopping coprocessor "+impl.getClass().getName()+
483             " because not active (state="+state.toString()+")");
484       }
485       synchronized (openTables) {
486         // clean up any table references
487         for (HTableInterface table: openTables) {
488           try {
489             ((HTableWrapper)table).internalClose();
490           } catch (IOException e) {
491             // nothing can be done here
492             LOG.warn("Failed to close " +
493                 Bytes.toStringBinary(table.getTableName()), e);
494           }
495         }
496       }
497     }
498 
499     @Override
500     public Coprocessor getInstance() {
501       return impl;
502     }
503 
504     @Override
505     public ClassLoader getClassLoader() {
506       return classLoader;
507     }
508 
509     @Override
510     public int getPriority() {
511       return priority;
512     }
513 
514     @Override
515     public int getLoadSequence() {
516       return seq;
517     }
518 
519     /** @return the coprocessor environment version */
520     @Override
521     public int getVersion() {
522       return Coprocessor.VERSION;
523     }
524 
525     /** @return the HBase release */
526     @Override
527     public String getHBaseVersion() {
528       return VersionInfo.getVersion();
529     }
530 
531     @Override
532     public Configuration getConfiguration() {
533       return conf;
534     }
535 
536     /**
537      * Open a table from within the Coprocessor environment
538      * @param tableName the table name
539      * @return an interface for manipulating the table
540      * @exception java.io.IOException Exception
541      */
542     @Override
543     public HTableInterface getTable(TableName tableName) throws IOException {
544       return this.getTable(tableName, HTable.getDefaultExecutor(getConfiguration()));
545     }
546 
547     /**
548      * Open a table from within the Coprocessor environment
549      * @param tableName the table name
550      * @return an interface for manipulating the table
551      * @exception java.io.IOException Exception
552      */
553     @Override
554     public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException {
555       return HTableWrapper.createWrapper(openTables, tableName, this, pool);
556     }
557   }
558 
559   protected void abortServer(final CoprocessorEnvironment environment, final Throwable e) {
560     abortServer(environment.getInstance().getClass().getName(), e);
561   }
562 
563   protected void abortServer(final String coprocessorName, final Throwable e) {
564     String message = "The coprocessor " + coprocessorName + " threw " + e.toString();
565     LOG.error(message, e);
566     if (abortable != null) {
567       abortable.abort(message, e);
568     } else {
569       LOG.warn("No available Abortable, process was not aborted");
570     }
571   }
572 
573   /**
574    * This is used by coprocessor hooks which are declared to throw IOException
575    * (or its subtypes). For such hooks, we should handle throwable objects
576    * depending on the Throwable's type. Those which are instances of
577    * IOException should be passed on to the client. This is in conformance with
578    * the HBase idiom regarding IOException: that it represents a circumstance
579    * that should be passed along to the client for its own handling. For
580    * example, a coprocessor that implements access controls would throw a
581    * subclass of IOException, such as AccessDeniedException, in its preGet()
582    * method to prevent an unauthorized client's performing a Get on a particular
583    * table.
584    * @param env Coprocessor Environment
585    * @param e Throwable object thrown by coprocessor.
586    * @exception IOException Exception
587    */
588   protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
589       throws IOException {
590     if (e instanceof IOException) {
591       throw (IOException)e;
592     }
593     // If we got here, e is not an IOException. A loaded coprocessor has a
594     // fatal bug, and the server (master or regionserver) should remove the
595     // faulty coprocessor from its set of active coprocessors. Setting
596     // 'hbase.coprocessor.abortonerror' to true will cause abortServer(),
597     // which may be useful in development and testing environments where
598     // 'failing fast' for error analysis is desired.
599     if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
600       // server is configured to abort.
601       abortServer(env, e);
602     } else {
603       // If available, pull a table name out of the environment
604       if(env instanceof RegionCoprocessorEnvironment) {
605         String tableName = ((RegionCoprocessorEnvironment)env).getRegionInfo().getTable().getNameAsString();
606         LOG.error("Removing coprocessor '" + env.toString() + "' from table '"+ tableName + "'", e);
607       } else {
608         LOG.error("Removing coprocessor '" + env.toString() + "' from " +
609                 "environment",e);
610       }
611 
612       coprocessors.remove(env);
613       try {
614         shutdown(env);
615       } catch (Exception x) {
616         LOG.error("Uncaught exception when shutting down coprocessor '"
617             + env.toString() + "'", x);
618       }
619       throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
620           "' threw: '" + e + "' and has been removed from the active " +
621           "coprocessor set.", e);
622     }
623   }
624 
625   /**
626    * Used to gracefully handle fallback to deprecated methods when we
627    * evolve coprocessor APIs.
628    *
629    * When a particular Coprocessor API is updated to change methods, hosts can support fallback
630    * to the deprecated API by using this method to determine if an instance implements the new API.
631    * In the event that said support is partial, then in the face of a runtime issue that prevents
632    * proper operation {@link #legacyWarning(Class, String)} should be used to let operators know.
633    *
634    * For examples of this in action, see the implementation of
635    * <ul>
636    *   <li>{@link org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost}
637    *   <li>{@link org.apache.hadoop.hbase.regionserver.wal.WALCoprocessorHost}
638    * </ul>
639    *
640    * @param clazz Coprocessor you wish to evaluate
641    * @param methodName the name of the non-deprecated method version
642    * @param parameterTypes the Class of the non-deprecated method's arguments in the order they are
643    *     declared.
644    */
645   @InterfaceAudience.Private
646   protected static boolean useLegacyMethod(final Class<? extends Coprocessor> clazz,
647       final String methodName, final Class<?>... parameterTypes) {
648     boolean useLegacy;
649     // Use reflection to see if they implement the non-deprecated version
650     try {
651       clazz.getDeclaredMethod(methodName, parameterTypes);
652       LOG.debug("Found an implementation of '" + methodName + "' that uses updated method " +
653           "signature. Skipping legacy support for invocations in '" + clazz +"'.");
654       useLegacy = false;
655     } catch (NoSuchMethodException exception) {
656       useLegacy = true;
657     } catch (SecurityException exception) {
658       LOG.warn("The Security Manager denied our attempt to detect if the coprocessor '" + clazz +
659           "' requires legacy support; assuming it does. If you get later errors about legacy " +
660           "coprocessor use, consider updating your security policy to allow access to the package" +
661           " and declared members of your implementation.");
662       LOG.debug("Details of Security Manager rejection.", exception);
663       useLegacy = true;
664     }
665     return useLegacy;
666   }
667 
668   /**
669    * Used to limit legacy handling to once per Coprocessor class per classloader.
670    */
671   private static final Set<Class<? extends Coprocessor>> legacyWarning =
672       new ConcurrentSkipListSet<Class<? extends Coprocessor>>(
673           new Comparator<Class<? extends Coprocessor>>() {
674             @Override
675             public int compare(Class<? extends Coprocessor> c1, Class<? extends Coprocessor> c2) {
676               if (c1.equals(c2)) {
677                 return 0;
678               }
679               return c1.getName().compareTo(c2.getName());
680             }
681           });
682 
683   /**
684    * limits the amount of logging to once per coprocessor class.
685    * Used in concert with {@link #useLegacyMethod(Class, String, Class[])} when a runtime issue
686    * prevents properly supporting the legacy version of a coprocessor API.
687    * Since coprocessors can be in tight loops this serves to limit the amount of log spam we create.
688    */
689   @InterfaceAudience.Private
690   protected void legacyWarning(final Class<? extends Coprocessor> clazz, final String message) {
691     if(legacyWarning.add(clazz)) {
692       LOG.error("You have a legacy coprocessor loaded and there are events we can't map to the " +
693           " deprecated API. Your coprocessor will not see these events.  Please update '" + clazz +
694           "'. Details of the problem: " + message);
695     }
696   }
697 }