/* Copyright (c) 2013-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: * Victor Olaya (Boundless) - initial implementation */ package org.locationtech.geogig.api.hooks; import java.io.File; import java.net.URISyntaxException; import java.net.URL; import java.util.List; import java.util.ServiceLoader; import javax.annotation.Nullable; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.Context; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * A class for managing GeoGig operations that can be hooked and the filenames of the corresponding * hooks. It also includes additional related utilities. * */ public class Hookables { private static final ImmutableList<CommandHook> classPathHooks; static { classPathHooks = Hookables.loadClasspathHooks(); } /** * Returns the filename to be used for a script corresponding to the hook for a given GeoGig * operation. Returns {@link Optional.absent} if the specified operation does not allows hooks * * @param class the operation * @return the string to be used as filename for storing the script files for the corresponding * hook */ public static Optional<String> getFilename(Class<? extends AbstractGeoGigOp<?>> clazz) { Hookable annotation = clazz.getAnnotation(Hookable.class); if (annotation != null) { return Optional.of(annotation.name()); } else { return Optional.absent(); } } public static ImmutableList<CommandHook> loadClasspathHooks() { ServiceLoader<CommandHook> loader = ServiceLoader.load(CommandHook.class); ImmutableList<CommandHook> SPIHooks = ImmutableList.copyOf(loader.iterator()); return SPIHooks; } public static boolean hasClasspathHooks(Class<? extends AbstractGeoGigOp<?>> commandClass) { for (CommandHook hook : classPathHooks) { if (hook.appliesTo(commandClass)) { return true; } } return false; } public static List<CommandHook> findHooksFor(AbstractGeoGigOp<?> operation) { @SuppressWarnings("unchecked") final Class<? extends AbstractGeoGigOp<?>> clazz = (Class<? extends AbstractGeoGigOp<?>>) operation.getClass(); List<CommandHook> hooks = Lists.newLinkedList(); /* * First add any classpath hook, as they can be added to any command, regardless of having * the @Hookable annotation or not */ for (CommandHook hook : classPathHooks) { if (hook.appliesTo(clazz)) { hooks.add(hook); } } /* * Now add any script hook that's configured for the operation iif it's @Hookable */ final Optional<String> name = Hookables.getFilename(clazz); if (!name.isPresent()) { return hooks; } final File hooksDir = findHooksDirectory(operation); if (hooksDir == null) { return hooks; } if (name.isPresent()) { String preHookName = "pre_" + name.get().toLowerCase(); String postHookName = "post_" + name.get().toLowerCase(); File[] files = hooksDir.listFiles(); for (File file : files) { String filename = file.getName(); if (isHook(filename, preHookName)) { hooks.add(Scripting.createScriptHook(file, true)); } if (isHook(filename, postHookName)) { hooks.add(Scripting.createScriptHook(file, false)); } } } return hooks; } /** * Looks up for the hooks directory in the repository {@code operation} works on. * <p> * Implementation note: this method must not create any command through * {@link Context#command(Class)} either directly or indirectly or a stack overflow exception * would be thrown * * @return {@code null} if the {@code operation} is not running on a repository, or the * repository has no {@code hooks} directory at all. */ @Nullable private static File findHooksDirectory(AbstractGeoGigOp<?> operation) { if (operation.context().repository() == null || operation.context().repository().getLocation() == null) { return null; } URL url = operation.context().repository().getLocation(); if (!"file".equals(url.getProtocol())) { // Hooks not in a filesystem are not supported return null; } File repoDir; try { repoDir = new File(url.toURI()); } catch (URISyntaxException e) { throw Throwables.propagate(e); } File hooksDir = new File(repoDir, "hooks"); if (!hooksDir.exists()) { return null; } return hooksDir; } private static boolean isHook(final String filename, final String hookNamePrefix) { if (hookNamePrefix.equals(filename) || filename.startsWith(hookNamePrefix + ".")) { if (!filename.endsWith("sample")) { return true; } } return false; } }