/* Copyright (c) 2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.api.hooks; import static com.google.common.base.Preconditions.checkState; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import javax.annotation.Nullable; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; public class CommandHookChain { private static final Logger LOGGER = LoggerFactory.getLogger(CommandHookChain.class); private AbstractGeoGigOp<?> target; private List<CommandHook> hooks; private CommandHookChain(final AbstractGeoGigOp<?> target) { this.target = target; this.hooks = new LinkedList<CommandHook>(); } public boolean isEmpty() { return hooks == null || hooks.isEmpty(); } void setNext(CommandHook next) { this.hooks.add(next); } public void runPreHooks() throws CannotRunGeogigOperationException { AbstractGeoGigOp<?> command = target; // run pre-hooks for (CommandHook hook : Lists.reverse(hooks)) { try { LOGGER.debug("Running pre command hook {}", hook); command = hook.pre(command); } catch (CannotRunGeogigOperationException e) { throw e; } } } public Object runPostHooks(@Nullable Object retVal, @Nullable RuntimeException exception) { AbstractGeoGigOp<?> command = target; for (CommandHook hook : hooks) { try { retVal = hook.post(command, retVal, exception); } catch (Exception e) { // this exception should not be thrown in a post-execution hook, but just in case, // we swallow it and ignore it LOGGER.warn( "Post-command hook {} for command {} threw an exception that will not be propagated", hook, command.getClass().getName(), e); } } return retVal; } public static Builder builder() { return new Builder(); } public static final class Builder { /** * Comparator that determines the priority of two hooks. In least to most important order: * {@link ShellScriptHook}, {@link JVMScriptHook}, other {@link CommandHook}s (i.e. pure * java ones) */ private static final Comparator<CommandHook> HOOKS_PRIORITY = new Comparator<CommandHook>() { @Override public int compare(CommandHook o1, CommandHook o2) { int p1 = o1 instanceof ShellScriptHook ? 1 : (o1 instanceof JVMScriptHook ? 0 : -1); int p2 = o2 instanceof ShellScriptHook ? 1 : (o2 instanceof JVMScriptHook ? 0 : -1); return p1 - p2; } }; private AbstractGeoGigOp<?> command; public Builder command(AbstractGeoGigOp<?> command) { this.command = command; return this; } public CommandHookChain build() { checkState(command != null, "command is null"); CommandHookChain chain = new CommandHookChain(command); List<CommandHook> commandHooks = Hookables.findHooksFor(command); if (!commandHooks.isEmpty()) { PriorityQueue<CommandHook> queue = new PriorityQueue<CommandHook>( commandHooks.size(), HOOKS_PRIORITY); queue.addAll(commandHooks); for (CommandHook hook : queue) { chain.setNext(hook); } } return chain; } } }