001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.boot;
019
020import com.mashape.unirest.http.Unirest;
021import io.stallion.dataAccess.db.SqlCheckNeedsMigrationAction;
022import io.stallion.dataAccess.db.SqlGenerationAction;
023import io.stallion.dataAccess.db.SqlMigrationAction;
024import io.stallion.exceptions.CommandException;
025import io.stallion.exceptions.UsageException;
026import io.stallion.plugins.PluginRegistry;
027import io.stallion.plugins.StallionJavaPlugin;
028import io.stallion.plugins.PluginTestsRunAction;
029import io.stallion.secrets.SecretsAction;
030import io.stallion.secrets.SecretsDecryptAction;
031import io.stallion.services.Log;
032import io.stallion.tools.ExportToHtml;
033import io.stallion.tools.ScriptExecBase;
034import io.stallion.users.UserAdder;
035import io.stallion.utils.Literals;
036import org.apache.commons.lang3.StringUtils;
037import org.kohsuke.args4j.CmdLineException;
038import org.kohsuke.args4j.CmdLineParser;
039
040import java.io.File;
041import java.io.IOException;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.List;
045import java.util.logging.Level;
046
047import static io.stallion.utils.Literals.*;
048
049/**
050 * Does the main work of booting up Stallion, and executing one of the boot actions.
051 */
052public class Booter {
053
054    private static final List<StallionRunAction> builtinActions = Literals.list(
055            new StallionServer(),
056            new UserAdder(),
057            new PluginTestsRunAction(),
058            new ScriptExecBase(),
059            new NewProjectBuilder(),
060            new NewDraftPageAction(),
061            //new NewJavaPluginRunAction(),
062            new ExportToHtml(),
063            new SecretsAction(),
064            new SqlMigrationAction(),
065            new SqlCheckNeedsMigrationAction(),
066            new SqlGenerationAction(),
067            new InteractiveJavascriptRunAction(),
068            new ForceTaskAction(),
069            new SecretsDecryptAction()
070    );
071
072    public void boot(String[] args) throws Exception {
073        boot(args, new StallionJavaPlugin[]{});
074    }
075
076    public void boot(String[] args, StallionJavaPlugin[] plugins) throws Exception {
077
078        // Load the plugin jars, to get additional actions
079        String targetPath = new File(".").getAbsolutePath();
080        for(String arg: args) {
081            if (arg.startsWith("-targetPath=")) {
082                targetPath = StringUtils.split(arg, "=", 2)[1];
083            }
084        }
085        if (!new File(targetPath).isDirectory() || empty(targetPath) || targetPath.equals("/") || targetPath.equals("/.")) {
086            throw new CommandException("You ran stallion with the target path " + targetPath + " but that is not a valid folder.");
087        }
088
089        PluginRegistry.loadWithJavaPlugins(targetPath, plugins);
090        List<StallionRunAction> actions = new ArrayList(builtinActions);
091        actions.addAll(PluginRegistry.instance().getAllPluginDefinedStallionRunActions());
092
093        // Load the action executor
094        StallionRunAction action = null;
095        for(StallionRunAction anAction: actions) {
096            if (empty(anAction.getActionName())) {
097                throw new UsageException("The action class " + anAction.getClass().getName() + " has an empty action name");
098            }
099            if (args.length > 0 && anAction.getActionName().equals(args[0])) {
100                action = anAction;
101            }
102        }
103        if (action == null) {
104            String msg = "\n\nError! You must pass in a valid action as the first command line argument. For example:\n\n" +
105                    ">stallion serve -port=8090 -targetPath=~/my-stallion-site\n";
106            if (args.length > 0) {
107                msg += "\n\nYou passed in '" + args[0] + "', which is not a valid action.\n";
108            }
109            msg += "\nAllowed actions are:\n\n";
110            for (StallionRunAction anAction: actions) {
111                //Log.warn("Action: {0} {1} {2}", action, action.getActionName(), action.getHelp());
112                msg += anAction.getActionName() + " - " + anAction.getHelp() + "\n";
113            }
114            msg += "\n\nYou can get help about an action by running: >stallion <action> help\n\n";
115            System.err.println(msg);
116            System.exit(1);
117        }
118
119
120
121        // Load the command line options
122        CommandOptionsBase options = action.newCommandOptions();
123
124        CmdLineParser parser = new CmdLineParser(options);
125
126        if (args.length > 1 && "help".equals(args[1])) {
127            System.out.println(action.getActionName() + " - " + action.getHelp() + "\n\n");
128            System.out.println("\n\nOptions for command " + action.getActionName() + "\n\n");
129            parser.printUsage(System.out);
130            System.exit(0);
131        }
132
133        try {
134            parser.parseArgument(args);
135        } catch( CmdLineException e ) {
136            System.err.println("\n\nError!\n\n" + e.getMessage());
137            System.err.println("\n\nAllowed options: \n");
138            parser.printUsage(System.err);
139            System.err.println("\n");
140            System.exit(1);
141        }
142
143        options.setExtraPlugins(Arrays.asList(plugins));
144
145        if (!empty(options.getLogLevel())) {
146            try {
147                Log.setLogLevel(Level.parse(options.getLogLevel()));
148            } catch (IllegalArgumentException e) {
149                System.err.println("\nError! Invalid log level: " + options.getLogLevel() + "\n\n" + CommandOptionsBase.ALLOWED_LEVELS);
150                System.exit(1);
151            }
152        }
153        if (empty(options.getTargetPath())) {
154            options.setTargetPath(new File(".").getAbsolutePath());
155        }
156
157        // Shutdown hooks
158        Thread shutDownHookThread = new Thread() {
159            public void run() {
160                try {
161                    Unirest.shutdown();
162                } catch (IOException e) {
163                    e.printStackTrace();
164                }
165            }
166        };
167        shutDownHookThread.setName("stallion-shutdown-hook");
168        Runtime.getRuntime().addShutdownHook(shutDownHookThread);
169
170
171        // Execute the action
172        action.loadApp(options);
173        action.execute(options);
174
175
176
177        if (!AppContextLoader.isNull()) {
178            AppContextLoader.shutdown();
179        }
180
181    }
182}