All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.plugincontext.PluginContext Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.plugincontext;

import static java.util.Objects.requireNonNull;

import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer3;
import com.google.gerrit.server.cancellation.RequestCancelledException;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * Context for invoking plugin extensions.
 *
 * 

Invoking a plugin extension through a PluginContext sets a logging tag with the plugin name is * set. This way any errors that are triggered by the plugin extension (even if they happen in * Gerrit code which is called by the plugin extension) can be easily attributed to the plugin. * *

If possible plugin extensions should be invoked through: * *

    *
  • {@link PluginItemContext} for extensions from {@link DynamicItem} *
  • {@link PluginSetContext} for extensions from {@link DynamicSet} *
  • {@link PluginMapContext} for extensions from {@link DynamicMap} *
* *

A plugin context can be manually opened by invoking the newTrace methods. This should only be * needed if an extension throws multiple exceptions that need to be handled: * *

{@code
 * public interface Foo {
 *   void doFoo() throws Exception1, Exception2, Exception3;
 * }
 *
 * ...
 *
 * for (Extension fooExtension : fooDynamicMap) {
 *   try (TraceContext traceContext = PluginContext.newTrace(fooExtension)) {
 *     fooExtension.get().doFoo();
 *   }
 * }
 * }
* *

This class hosts static methods with generic functionality to invoke plugin extensions with a * trace context that are commonly used by {@link PluginItemContext}, {@link PluginSetContext} and * {@link PluginMapContext}. * *

The run* methods execute an extension but don't deliver a result back to the caller. * Exceptions can be caught and logged. * *

The call* methods execute an extension and deliver a result back to the caller. */ public class PluginContext { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @FunctionalInterface public interface ExtensionImplConsumer { void run(T t) throws Exception; } @FunctionalInterface public interface ExtensionImplFunction { R call(T input); } @FunctionalInterface public interface CheckedExtensionImplFunction { R call(T input) throws X; } @FunctionalInterface public interface ExtensionConsumer> { void run(T extension) throws Exception; } @FunctionalInterface public interface ExtensionFunction, R> { R call(T extension); } @FunctionalInterface public interface CheckedExtensionFunction, R, X extends Exception> { R call(T extension) throws X; } @Singleton public static class PluginMetrics { public static final PluginMetrics DISABLED_INSTANCE = new PluginMetrics(new DisabledMetricMaker()); final Timer3 latency; final Counter3 errorCount; @Inject PluginMetrics(MetricMaker metricMaker) { Field pluginNameField = Field.ofString("plugin_name", Metadata.Builder::pluginName) .description("The name of the plugin.") .build(); Field classNameField = Field.ofString("class_name", Metadata.Builder::className) .description("The class of the plugin that was invoked.") .build(); Field exportValueField = Field.ofString("export_value", Metadata.Builder::exportValue) .description("The export name under which the invoked class is registered.") .build(); this.latency = metricMaker.newTimer( "plugin/latency", new Description("Latency for plugin invocation") .setCumulative() .setUnit(Units.MILLISECONDS), pluginNameField, classNameField, exportValueField); this.errorCount = metricMaker.newCounter( "plugin/error_count", new Description("Number of plugin errors").setCumulative().setUnit("errors"), pluginNameField, classNameField, exportValueField); } Timer3.Context startLatency(Extension extension) { return latency.start( extension.getPluginName(), extension.get().getClass().getName(), Strings.nullToEmpty(extension.getExportName())); } void incrementErrorCount(Extension extension) { errorCount.increment( extension.getPluginName(), extension.get().getClass().getName(), Strings.nullToEmpty(extension.getExportName())); } } /** * Opens a new trace context for invoking a plugin extension. * * @param dynamicItem dynamic item that holds the extension implementation that is being invoked * from within the trace context * @return the created trace context */ public static TraceContext newTrace(DynamicItem dynamicItem) { Extension extension = dynamicItem.getEntry(); if (extension == null) { return TraceContext.open(); } return newTrace(extension); } /** * Opens a new trace context for invoking a plugin extension. * * @param extension extension that is being invoked from within the trace context * @return the created trace context */ public static TraceContext newTrace(Extension extension) { return TraceContext.open().addPluginTag(requireNonNull(extension).getPluginName()); } /** * Runs a plugin extension. All exceptions from the plugin extension are caught and logged (except * {@link RequestCancelledException}. * *

The consumer gets the extension implementation provided that should be invoked. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param extensionImplConsumer the consumer that invokes the extension */ static void runLogExceptions( PluginMetrics pluginMetrics, Extension extension, ExtensionImplConsumer extensionImplConsumer) { T extensionImpl = extension.get(); if (extensionImpl == null) { return; } try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { extensionImplConsumer.run(extensionImpl); } catch (Exception e) { Throwables.throwIfInstanceOf(e, RequestCancelledException.class); pluginMetrics.incrementErrorCount(extension); logger.atWarning().withCause(e).log( "Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName()); } } /** * Runs a plugin extension. All exceptions from the plugin extension are caught and logged (except * {@link RequestCancelledException}. * *

The consumer get the {@link Extension} provided that should be invoked. The extension * provides access to the plugin name and the export name. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param extensionConsumer the consumer that invokes the extension */ static void runLogExceptions( PluginMetrics pluginMetrics, Extension extension, ExtensionConsumer> extensionConsumer) { T extensionImpl = extension.get(); if (extensionImpl == null) { return; } try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { extensionConsumer.run(extension); } catch (Exception e) { Throwables.throwIfInstanceOf(e, RequestCancelledException.class); pluginMetrics.incrementErrorCount(extension); logger.atWarning().withCause(e).log( "Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName()); } } /** * Runs a plugin extension. All exceptions from the plugin extension except exceptions of the * specified type are caught and logged. Exceptions of the specified type are thrown and must be * handled by the caller. * *

The consumer gets the extension implementation provided that should be invoked. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param extensionImplConsumer the consumer that invokes the extension * @param exceptionClass type of the exceptions that should be thrown * @throws X expected exception from the plugin extension */ static void runLogExceptions( PluginMetrics pluginMetrics, Extension extension, ExtensionImplConsumer extensionImplConsumer, Class exceptionClass) throws X { T extensionImpl = extension.get(); if (extensionImpl == null) { return; } try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { extensionImplConsumer.run(extensionImpl); } catch (Exception e) { Throwables.throwIfInstanceOf(e, exceptionClass); Throwables.throwIfUnchecked(e); pluginMetrics.incrementErrorCount(extension); logger.atWarning().withCause(e).log( "Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName()); } } /** * Runs a plugin extension. All exceptions from the plugin extension except exceptions of the * specified type are caught and logged. Exceptions of the specified type are thrown and must be * handled by the caller. * *

The consumer get the {@link Extension} provided that should be invoked. The extension * provides access to the plugin name and the export name. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param extensionConsumer the consumer that invokes the extension * @param exceptionClass type of the exceptions that should be thrown * @throws X expected exception from the plugin extension */ static void runLogExceptions( PluginMetrics pluginMetrics, Extension extension, ExtensionConsumer> extensionConsumer, Class exceptionClass) throws X { T extensionImpl = extension.get(); if (extensionImpl == null) { return; } try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { extensionConsumer.run(extension); } catch (Exception e) { Throwables.throwIfInstanceOf(e, exceptionClass); Throwables.throwIfUnchecked(e); pluginMetrics.incrementErrorCount(extension); logger.atWarning().withCause(e).log( "Failure in %s of plugin %s", extensionImpl.getClass(), extension.getPluginName()); } } /** * Calls a plugin extension and returns the result from the plugin extension call. * *

The function gets the extension implementation provided that should be invoked. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param extensionImplFunction function that invokes the extension * @return the result from the plugin extension */ static R call( PluginMetrics pluginMetrics, Extension extension, ExtensionImplFunction extensionImplFunction) { try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { return extensionImplFunction.call(extension.get()); } } /** * Calls a plugin extension and returns the result from the plugin extension call. Exceptions of * the specified type are thrown and must be handled by the caller. * *

The function gets the extension implementation provided that should be invoked. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param checkedExtensionImplFunction function that invokes the extension * @param exceptionClass type of the exceptions that should be thrown * @return the result from the plugin extension * @throws X expected exception from the plugin extension */ static R call( PluginMetrics pluginMetrics, Extension extension, CheckedExtensionImplFunction checkedExtensionImplFunction, Class exceptionClass) throws X { try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { try { return checkedExtensionImplFunction.call(extension.get()); } catch (Exception e) { // The only exception that can be thrown is X, but we cannot catch X since it is a generic // type. Throwables.throwIfInstanceOf(e, exceptionClass); Throwables.throwIfUnchecked(e); throw new IllegalStateException("unexpected exception: " + e.getMessage(), e); } } } /** * Calls a plugin extension and returns the result from the plugin extension call. * *

The function get the {@link Extension} provided that should be invoked. The extension * provides access to the plugin name and the export name. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param extensionFunction function that invokes the extension * @return the result from the plugin extension */ static R call( PluginMetrics pluginMetrics, Extension extension, ExtensionFunction, R> extensionFunction) { try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { return extensionFunction.call(extension); } } /** * Calls a plugin extension and returns the result from the plugin extension call. Exceptions of * the specified type are thrown and must be handled by the caller. * *

The function get the {@link Extension} provided that should be invoked. The extension * provides access to the plugin name and the export name. * * @param pluginMetrics the plugin metrics * @param extension extension that is being invoked * @param checkedExtensionFunction function that invokes the extension * @param exceptionClass type of the exceptions that should be thrown * @return the result from the plugin extension * @throws X expected exception from the plugin extension */ static R call( PluginMetrics pluginMetrics, Extension extension, CheckedExtensionFunction, R, X> checkedExtensionFunction, Class exceptionClass) throws X { try (TraceContext traceContext = newTrace(extension); Timer3.Context ctx = pluginMetrics.startLatency(extension)) { try { return checkedExtensionFunction.call(extension); } catch (Exception e) { // The only exception that can be thrown is X, but we cannot catch X since it is a generic // type. Throwables.throwIfInstanceOf(e, exceptionClass); Throwables.throwIfUnchecked(e); throw new IllegalStateException("unexpected exception: " + e.getMessage(), e); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy