Java tutorial
/* * Copyright (c) 2015 Spotify AB. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.spotify.heroic; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.spotify.heroic.HeroicCore.Builder; import com.spotify.heroic.args4j.CmdLine; import com.spotify.heroic.shell.AbstractShellTaskParams; import com.spotify.heroic.shell.CoreInterface; import com.spotify.heroic.shell.RemoteCoreInterface; import com.spotify.heroic.shell.ShellIO; import com.spotify.heroic.shell.ShellProtocol; import com.spotify.heroic.shell.ShellTask; import com.spotify.heroic.shell.TaskParameters; import com.spotify.heroic.shell.protocol.CommandDefinition; import eu.toolchain.async.AsyncFramework; import eu.toolchain.async.AsyncFuture; import eu.toolchain.async.TinyAsync; import eu.toolchain.serializer.SerializerFramework; import lombok.RequiredArgsConstructor; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.Executors; @Slf4j public class HeroicShell { public static final Path[] DEFAULT_CONFIGS = new Path[] { Paths.get("heroic.yml"), Paths.get("/etc/heroic/heroic.yml") }; public static final SerializerFramework serializer = ShellProtocol.setupSerializer(); public static void main(String[] args) throws IOException { HeroicLogging.configure(); Thread.setDefaultUncaughtExceptionHandler((t, e) -> { try { log.error("Uncaught exception in thread {}, exiting...", t, e); } finally { System.exit(1); } }); final Parameters params = new Parameters(); final CmdLineParser parser = setupParser(params); final ParsedArguments parsed = ParsedArguments.parse(args); try { parser.parseArgument(parsed.primary); } catch (CmdLineException e) { log.error("Argument error", e); System.exit(1); return; } if (params.help()) { parser.printUsage(System.out); System.out.println(); HeroicModules.printAllUsage(System.out, "-P"); System.exit(0); return; } final AsyncFramework async = TinyAsync.builder().executor(Executors.newSingleThreadExecutor()).build(); if (parsed.child.isEmpty()) { final CoreInterface bridge; try { bridge = setupCoreBridge(params, async); } catch (Exception e) { log.error("Failed to setup core bridge", e); System.exit(1); return; } try { interactive(params, bridge); } catch (Exception e) { log.error("Error when running shell", e); System.exit(1); } System.exit(0); return; } final HeroicCore.Builder builder = setupBuilder(params); try { standalone(parsed.child, builder); } catch (Exception e) { log.error("Failed to run standalone task", e); } System.exit(0); } private static CoreInterface setupCoreBridge(Parameters params, AsyncFramework async) throws Exception { if (params.connect != null) { return setupRemoteCore(params.connect, async); } return setupLocalCore(params, async); } private static CoreInterface setupRemoteCore(String connect, AsyncFramework async) throws Exception { return RemoteCoreInterface.fromConnectString(connect, async, serializer); } private static CoreInterface setupLocalCore(Parameters params, AsyncFramework async) throws Exception { final HeroicCore.Builder builder = setupBuilder(params); final HeroicCore core = builder.build(); log.info("Starting local Heroic..."); final HeroicCoreInstance instance = core.newInstance(); instance.start().get(); return instance.<CoreInterface>inject(comp -> new CoreInterface() { private final ShellTasks tasks = comp.tasks(); @Override public AsyncFuture<Void> evaluate(List<String> command, ShellIO io) throws Exception { return tasks.evaluate(command, io); } @Override public List<CommandDefinition> commands() throws Exception { return tasks.commands(); } @Override public void shutdown() throws Exception { instance.shutdown().get(); } }); } static void interactive(Parameters params, CoreInterface core) throws Exception { log.info("Setting up interactive shell..."); Exception e = null; try { runInteractiveShell(core); } catch (final Exception inner) { e = inner; } log.info("Closing core bridge..."); try { core.shutdown(); } catch (final Exception inner) { if (e != null) { inner.addSuppressed(e); } e = inner; } if (e != null) { throw e; } } static void runInteractiveShell(final CoreInterface core) throws Exception { final List<CommandDefinition> commands = new ArrayList<>(core.commands()); commands.add(new CommandDefinition("clear", ImmutableList.of(), "Clear the current shell")); commands.add(new CommandDefinition("timeout", ImmutableList.of(), "Get or set the current task timeout")); commands.add(new CommandDefinition("exit", ImmutableList.of(), "Exit the shell")); try (final FileInputStream input = new FileInputStream(FileDescriptor.in)) { final HeroicInteractiveShell interactive = HeroicInteractiveShell.buildInstance(commands, input); try { interactive.run(core); } finally { interactive.shutdown(); } } } static void standalone(List<String> arguments, Builder builder) throws Exception { final String taskName = arguments.iterator().next(); final List<String> rest = arguments.subList(1, arguments.size()); log.info("Running standalone task {}", taskName); final HeroicCore core = builder.build(); log.info("Starting Heroic..."); final HeroicCoreInstance instance = core.newInstance(); instance.start().get(); final ShellTask task = instance.inject(c -> c.tasks().resolve(taskName)); final TaskParameters params = task.params(); final CmdLineParser parser = setupParser(params); try { parser.parseArgument(rest); } catch (CmdLineException e) { log.error("Error parsing arguments", e); System.exit(1); return; } if (params.help()) { parser.printUsage(System.err); HeroicModules.printAllUsage(System.err, "-P"); System.exit(0); return; } try { final PrintWriter o = standaloneOutput(params, System.out); final ShellIO io = new DirectShellIO(o); try { task.run(io, params).get(); } catch (Exception e) { log.error("Failed to run task", e); } finally { o.flush(); } } finally { instance.shutdown().get(); } } @SuppressWarnings("unchecked") static Class<ShellTask> resolveShellTask(final String taskName) throws ClassNotFoundException, Exception { final Class<?> taskType = Class.forName(taskName); if (!(ShellTask.class.isAssignableFrom(taskType))) { throw new Exception(String.format("Not an instance of ShellTask (%s)", taskName)); } return (Class<ShellTask>) taskType; } static PrintWriter standaloneOutput(final TaskParameters params, final PrintStream original) throws IOException { final OutputStream out; if (params.output() != null && !"-".equals(params.output())) { out = Files.newOutputStream(Paths.get(params.output())); } else { out = original; } return new PrintWriter(new OutputStreamWriter(out, Charsets.UTF_8)); } static Path parseConfigPath(String config) { final Path path = doParseConfigPath(config); if (!Files.isRegularFile(path)) { throw new IllegalStateException("No such file: " + path.toAbsolutePath()); } return path; } static Path doParseConfigPath(String config) { if (config == null) { for (final Path p : DEFAULT_CONFIGS) { if (Files.isRegularFile(p)) { return p; } } throw new IllegalStateException( "No default configuration available, checked " + formatDefaults(DEFAULT_CONFIGS)); } return Paths.get(config); } static String formatDefaults(Path[] defaultConfigs) { final List<Path> alternatives = new ArrayList<>(defaultConfigs.length); for (final Path path : defaultConfigs) { alternatives.add(path.toAbsolutePath()); } return StringUtils.join(alternatives, ", "); } static HeroicCore.Builder setupBuilder(Parameters params) { HeroicCore.Builder builder = HeroicCore.builder().setupService(params.server) .disableBackends(params.disableBackends).modules(HeroicModules.ALL_MODULES).oneshot(true); if (params.config() != null) { builder.configPath(parseConfigPath(params.config())); } builder.parameters(ExtraParameters.ofList(params.parameters)); for (final String profile : params.profiles()) { final HeroicProfile p = HeroicModules.PROFILES.get(profile); if (p == null) { throw new IllegalArgumentException(String.format("not a valid profile: %s", profile)); } builder.profile(p); } builder.setupShellServer(params.shellServer); return builder; } /** * Setup a {@link org.kohsuke.args4j.CmdLineParser} with some useful handlers associated with * it. */ private static CmdLineParser setupParser(final TaskParameters params) { return CmdLine.createParser(params); } @ToString public static class Parameters extends AbstractShellTaskParams { @Option(name = "--server", usage = "Start shell as server (enables listen port)") private boolean server = false; @Option(name = "--shell-server", usage = "Start shell with shell server (enables remote connections)") private boolean shellServer = false; @Option(name = "--disable-backends", usage = "Start core without configuring backends") private boolean disableBackends = false; @Option(name = "--connect", usage = "Connect to a remote heroic server", metaVar = "<host>[:<port>]") private String connect = null; @Option(name = "-X", usage = "Define an extra parameter", metaVar = "<key>=<value>") private List<String> parameters = new ArrayList<>(); } @RequiredArgsConstructor static class ParsedArguments { final List<String> primary; final List<String> child; public static ParsedArguments parse(String[] args) { final List<String> primary = new ArrayList<>(); final List<String> child = new ArrayList<>(); final Iterator<String> iterator = Arrays.stream(args).iterator(); while (iterator.hasNext()) { final String arg = iterator.next(); if ("--".equals(arg)) { break; } primary.add(arg); } while (iterator.hasNext()) { child.add(iterator.next()); } return new ParsedArguments(primary, child); } } }