Package org.locationtech.geogig.api.hooks

Source Code of org.locationtech.geogig.api.hooks.Scripting$StreamGobbler

/* 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 static com.google.common.base.Preconditions.checkArgument;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.plumbing.ResolveRepository;
import org.locationtech.geogig.repository.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Files;

/**
* Utilities to execute scripts representing hooks for GeoGig operations
*
*/
public class Scripting {

    private static final Logger LOGGER = LoggerFactory.getLogger(Scripting.class);

    private static final String PARAMS = "params";

    private static final String GEOGIG = "geogig";

    private static ScriptEngineManager factory = new ScriptEngineManager();

    /**
     * Runs a script
     *
     * @param scriptFile the script file to run
     * @param operation the operation triggering the script, to provide context for the script. This
     *        object might get modified if the script modifies it to alter how the command is called
     *        (for instance, changing the commit message in a commit operation)
     * @throws CannotRunGeogigOperationException
     */
    @SuppressWarnings("unchecked")
    public static void runJVMScript(AbstractGeoGigOp<?> operation, File scriptFile)
            throws CannotRunGeogigOperationException {

        checkArgument(scriptFile.exists(), "Script file does not exist %s", scriptFile.getPath());

        LOGGER.info("Running jvm script {}", scriptFile.getAbsolutePath());
        final String filename = scriptFile.getName();
        final String ext = Files.getFileExtension(filename);

        final ScriptEngine engine = factory.getEngineByExtension(ext);

        try {
            Map<String, Object> params = getParamMap(operation);
            engine.put(PARAMS, params);
            Repository repo = operation.command(ResolveRepository.class).call();
            GeoGigAPI api = new GeoGigAPI(repo);
            engine.put(GEOGIG, api);
            engine.eval(new FileReader(scriptFile));
            Object map = engine.get(PARAMS);
            setParamMap((Map<String, Object>) map, operation);
        } catch (ScriptException e) {
            Throwable cause = Throwables.getRootCause(e);
            // TODO: improve this hack to check exception type
            if (cause != e) {
                String msg = cause.getMessage();
                msg = msg.substring(CannotRunGeogigOperationException.class.getName().length() + 2,
                        msg.lastIndexOf("(")).trim();
                msg += " (command aborted by .geogig/hooks/" + scriptFile.getName() + ")";
                throw new CannotRunGeogigOperationException(msg);
            } else {
                throw new CannotRunGeogigOperationException(String.format(
                        "Script %s threw an exception: '%s'", scriptFile, e.getMessage()), e);
            }
        } catch (Exception e) {
        }
    }

    public static void runShellScript(final File scriptFile)
            throws CannotRunGeogigOperationException {

        LOGGER.info("Running shell script {}", scriptFile.getAbsolutePath());

        // try running the script directly as an executable file
        List<String> commandAndArgs = Lists.newArrayList();
        ProcessBuilder pb = new ProcessBuilder(commandAndArgs);
        if (isWindows()) {
            commandAndArgs.add("cmd.exe");
            commandAndArgs.add("/C");
            commandAndArgs.add(scriptFile.getPath());
        } else {
            if (!scriptFile.canExecute()) {
                return;
            }
            commandAndArgs.add(scriptFile.getPath());
        }

        try {
            LOGGER.debug("-- starting process {}", scriptFile);
            pb.redirectErrorStream(true);
            final Process process = pb.start();
            LOGGER.debug("-- process {} started", scriptFile);

            final StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(),
                    System.out);
            outputGobbler.start();
            int exitCode;
            try {
                LOGGER.debug("-- waiting for process {} to finish", scriptFile);
                exitCode = process.waitFor();
                LOGGER.debug("process {} exit code: {}", scriptFile, exitCode);
            } finally {
                outputGobbler.stop();
            }
            if (exitCode != 0) {
                // the script exited with non-zero code, so we indicate it throwing the
                // corresponding exception.
                // TODO: get message?
                throw new CannotRunGeogigOperationException(
                        "Hook script exited with non-zero error code");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return; // can't run scripts, so there is nothing that blocks running the
                    // command, and we can return
        } catch (InterruptedException e) {
            e.printStackTrace();
            return; // can't run scripts, so there is nothing that blocks running the
                    // command, and we can return

        }
    }

    /**
     * Method for getting values of parameters, including private fields. This is to be used from
     * scripting languages to create hooks for available commands. TODO: Review this and maybe
     * change this way of accessing values
     *
     * @param operation
     *
     * @param param the name of the parameter
     * @return the value of the parameter
     */
    public static Map<String, Object> getParamMap(AbstractGeoGigOp<?> operation) {
        Map<String, Object> map = Maps.newHashMap();
        try {
            Field[] fields = operation.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String name = field.getName();
                Object value = field.get(operation);
                map.put(name, value);
            }
        } catch (SecurityException e) {
            return map;
        } catch (IllegalArgumentException e) {
            return map;
        } catch (IllegalAccessException e) {
            return map;
        }
        return map;

    }

    /**
     * Method to set fields in the operation object. This is to be used to communicate with script
     * hooks, so the operation can be modified in the hook, changing the values of its fields.
     * Entries corresponding to inexistent fields are ignored
     *
     * @param operation
     *
     * @param a map of new field values. Keys are field names
     */
    public static void setParamMap(Map<String, Object> map, AbstractGeoGigOp<?> operation) {
        try {
            Field[] fields = operation.getClass().getDeclaredFields();
            Set<String> keys = map.keySet();
            for (Field field : fields) {
                final int modifiers = field.getModifiers();
                if (field.isSynthetic() || Modifier.isStatic(modifiers)
                        || Modifier.isFinal(modifiers)) {
                    continue;
                }
                if (keys.contains(field.getName())) {
                    field.setAccessible(true);
                    field.set(operation, map.get(field.getName()));
                }
            }
        } catch (Exception e) {
            // if the script contains wrong variables, and it causes exceptions, or there
            // is any other problem, we just ignore it, and the original command will be executed
        }

    }

    public static boolean isWindows() {
        final String os = System.getProperty("os.name").toLowerCase();
        return (os.indexOf("win") >= 0);
    }

    private static class StreamGobbler extends Thread {

        InputStream is;

        OutputStream out;

        StreamGobbler(final InputStream is, final OutputStream out) {
            this.is = is;
            this.out = out;
            setDaemon(true);
        }

        @Override
        public void run() {
            try {
                int c;
                while ((c = is.read()) != -1) {
                    out.write(c);
                }
            } catch (final IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

    public static CommandHook createScriptHook(final File file, final boolean preHook) {
        final String filename = file.getName();
        final String ext = Files.getFileExtension(filename);

        final File preScript = preHook ? file : null;
        final File postScript = preHook ? null : file;

        final CommandHook hook;
        final ScriptEngine engine = factory.getEngineByExtension(ext);
        if (engine == null) {
            hook = new ShellScriptHook(preScript, postScript);
        } else {
            hook = new JVMScriptHook(preScript, postScript);
        }
        return hook;
    }
}
TOP

Related Classes of org.locationtech.geogig.api.hooks.Scripting$StreamGobbler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.