Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.plugincontext; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : 19 : import com.google.common.annotations.VisibleForTesting; 20 : import com.google.gerrit.common.Nullable; 21 : import com.google.gerrit.extensions.registration.DynamicItem; 22 : import com.google.gerrit.extensions.registration.Extension; 23 : import com.google.gerrit.server.plugincontext.PluginContext.CheckedExtensionImplFunction; 24 : import com.google.gerrit.server.plugincontext.PluginContext.ExtensionImplConsumer; 25 : import com.google.gerrit.server.plugincontext.PluginContext.ExtensionImplFunction; 26 : import com.google.gerrit.server.plugincontext.PluginContext.PluginMetrics; 27 : import com.google.inject.Inject; 28 : 29 : /** 30 : * Context to invoke an extension from a {@link DynamicItem}. 31 : * 32 : * <p>When the plugin extension is invoked a logging tag with the plugin name is set. This way any 33 : * errors that are triggered by the plugin extension (even if they happen in Gerrit code which is 34 : * called by the plugin extension) can be easily attributed to the plugin. 35 : * 36 : * <p>The run* methods execute an extension but don't deliver a result back to the caller. 37 : * Exceptions can be caught and logged. 38 : * 39 : * <p>The call* methods execute an extension and deliver a result back to the caller. 40 : * 41 : * <p>Example if all exceptions should be caught and logged: 42 : * 43 : * <pre>{@code 44 : * fooPluginItemContext.run(foo -> foo.doFoo()); 45 : * }</pre> 46 : * 47 : * <p>Example if all exceptions, but one, should be caught and logged: 48 : * 49 : * <pre>{@code 50 : * try { 51 : * fooPluginItemContext.run(foo -> foo.doFoo(), MyException.class); 52 : * } catch (MyException e) { 53 : * // handle the exception 54 : * } 55 : * }</pre> 56 : * 57 : * <p>Example if return values should be handled: 58 : * 59 : * <pre>{@code 60 : * Object result = fooPluginItemContext.call(foo -> foo.getFoo()); 61 : * }</pre> 62 : * 63 : * <p>Example if return values and a single exception should be handled: 64 : * 65 : * <pre>{@code 66 : * Object result; 67 : * try { 68 : * result = fooPluginItemContext.call(foo -> foo.getFoo(), MyException.class); 69 : * } catch (MyException e) { 70 : * // handle the exception 71 : * } 72 : * }</pre> 73 : * 74 : * <p>Example if several exceptions should be handled: 75 : * 76 : * <pre>{@code 77 : * try (TraceContext traceContext = PluginContext.newTrace(fooDynamicItem.getEntry())) { 78 : * fooDynamicItem.get().doFoo(); 79 : * } catch (MyException1 | MyException2 | MyException3 e) { 80 : * // handle the exception 81 : * } 82 : * }</pre> 83 : */ 84 : public class PluginItemContext<T> { 85 : @Nullable private final DynamicItem<T> dynamicItem; 86 : private final PluginMetrics pluginMetrics; 87 : 88 : @VisibleForTesting 89 : @Inject 90 149 : public PluginItemContext(DynamicItem<T> dynamicItem, PluginMetrics pluginMetrics) { 91 149 : this.dynamicItem = dynamicItem; 92 149 : this.pluginMetrics = pluginMetrics; 93 149 : } 94 : 95 : /** 96 : * Checks if an implementation for this extension point has been registered. 97 : * 98 : * @return {@code true} if an implementation for this extension point has been registered, 99 : * otherwise {@code false} 100 : */ 101 : public boolean hasImplementation() { 102 3 : return dynamicItem.getEntry() != null; 103 : } 104 : 105 : /** 106 : * Returns the name of the plugin that registered the extension. 107 : * 108 : * @return the plugin name, {@code null} if no implementation is registered for this extension 109 : * point 110 : */ 111 : @Nullable 112 : public String getPluginName() { 113 0 : return dynamicItem.getPluginName(); 114 : } 115 : 116 : /** 117 : * Invokes the plugin extension of the item. All exceptions from the plugin extension are caught 118 : * and logged. 119 : * 120 : * <p>The consumer gets the extension implementation provided that should be invoked. 121 : * 122 : * <p>No-op if no implementation is registered for this extension point. 123 : * 124 : * @param extensionImplConsumer consumer that invokes the extension 125 : */ 126 : public void run(ExtensionImplConsumer<T> extensionImplConsumer) { 127 142 : Extension<T> extension = dynamicItem.getEntry(); 128 142 : if (extension == null) { 129 4 : return; 130 : } 131 138 : PluginContext.runLogExceptions(pluginMetrics, extension, extensionImplConsumer); 132 138 : } 133 : 134 : /** 135 : * Invokes the plugin extension of the item. All exceptions from the plugin extension are caught 136 : * and logged. 137 : * 138 : * <p>The consumer gets the extension implementation provided that should be invoked. 139 : * 140 : * <p>No-op if no implementation is registered for this extension point. 141 : * 142 : * @param extensionImplConsumer consumer that invokes the extension 143 : * @param exceptionClass type of the exceptions that should be thrown 144 : * @throws X expected exception from the plugin extension 145 : */ 146 : public <X extends Exception> void run( 147 : ExtensionImplConsumer<T> extensionImplConsumer, Class<X> exceptionClass) throws X { 148 0 : Extension<T> extension = dynamicItem.getEntry(); 149 0 : if (extension == null) { 150 0 : return; 151 : } 152 0 : PluginContext.runLogExceptions(pluginMetrics, extension, extensionImplConsumer, exceptionClass); 153 0 : } 154 : 155 : /** 156 : * Calls the plugin extension of the item and returns the result from the plugin extension call. 157 : * 158 : * <p>The function gets the extension implementation provided that should be invoked. 159 : * 160 : * <p>Fails with {@link IllegalStateException} if no implementation is registered for the item. 161 : * 162 : * @param extensionImplFunction function that invokes the extension 163 : * @return the result from the plugin extension 164 : * @throws IllegalStateException if no implementation is registered for the item 165 : */ 166 : public <R> R call(ExtensionImplFunction<T, R> extensionImplFunction) { 167 144 : Extension<T> extension = dynamicItem.getEntry(); 168 144 : checkState(extension != null); 169 144 : return PluginContext.call(pluginMetrics, extension, extensionImplFunction); 170 : } 171 : 172 : /** 173 : * Calls the plugin extension of the item and returns the result from the plugin extension call. 174 : * Exceptions of the specified type are thrown and must be handled by the caller. 175 : * 176 : * <p>The function gets the extension implementation provided that should be invoked. 177 : * 178 : * <p>Fails with {@link IllegalStateException} if no implementation is registered for the item. 179 : * 180 : * @param checkedExtensionImplFunction function that invokes the extension 181 : * @param exceptionClass type of the exceptions that should be thrown 182 : * @return the result from the plugin extension 183 : * @throws X expected exception from the plugin extension 184 : * @throws IllegalStateException if no implementation is registered for the item 185 : */ 186 : public <R, X extends Exception> R call( 187 : CheckedExtensionImplFunction<T, R, X> checkedExtensionImplFunction, Class<X> exceptionClass) 188 : throws X { 189 0 : Extension<T> extension = dynamicItem.getEntry(); 190 0 : checkState(extension != null); 191 0 : return PluginContext.call( 192 : pluginMetrics, extension, checkedExtensionImplFunction, exceptionClass); 193 : } 194 : }