001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.q2.qbean;
020
021import org.jpos.q2.QBeanSupport;
022import org.jpos.util.LogEvent;
023import org.jpos.util.Logger;
024
025import java.io.*;
026import java.util.ArrayList;
027import java.util.List;
028
029/**
030* QBean for starting and stopping scripts or programs.
031* <pre>
032* Example xml:
033*  &lt;exec class="org.jpos.q2.qbean.QExec"&gt;
034*    &lt;attr name="startScript"&gt;YOUR PATH TO PROGRAM&lt;/attr&gt;
035*    &lt;attr name="shutdownScript"&gt;YOUR PATH TO PROGRAM&lt;/attr&gt;
036*  &lt;/exec&gt;
037* </pre>
038* @author Alwyn Schoeman
039* @version $Revision$ $Date$
040*/
041
042public class QExec extends QBeanSupport implements QExecMBean {
043    /** Default constructor; no instance state to initialise. */
044    public QExec() {}
045    String startScript;
046    String shutdownScript;
047
048    @Override
049    protected void startService () throws Exception {
050        exec(startScript);
051    }
052
053    @Override
054    protected void stopService () throws Exception {
055        exec(shutdownScript);
056    }
057
058    public void setStartScript (String scriptPath) {
059        startScript = scriptPath;
060    }
061
062    public String getStartScript () {
063        return startScript;
064    }
065
066    public void setShutdownScript (String scriptPath) {
067        shutdownScript = scriptPath;
068    }
069
070    public String getShutdownScript () {
071        return shutdownScript;
072    }
073
074    private void exec (String script) throws IOException, InterruptedException {
075        if (script != null) {
076            ProcessBuilder pb = new ProcessBuilder (parseCommandLine(script));
077            Process p = pb.start();
078            BufferedReader in = p.inputReader();
079            LogEvent evt = getLog().createInfo("--- " + script + " ---");
080            String line;
081            while ((line = in.readLine()) != null) {
082                evt.addMessage(line);
083            }
084            p.waitFor();
085            Logger.log(evt);
086        }
087    }
088
089    /**
090     * Tokenises a command line, honouring double-quoted segments and
091     * backslash-escaped characters.
092     *
093     * @param commandLine the command-line string
094     * @return the parsed argument list
095     * @throws IllegalArgumentException if {@code commandLine} is {@code null} or empty
096     */
097    public static List<String> parseCommandLine(String commandLine) {
098        if (commandLine == null || commandLine.isEmpty())
099            throw new IllegalArgumentException("Empty command");
100        List<String> args = new ArrayList<>();
101        StringBuilder currentArg = new StringBuilder();
102        boolean inQuotes = false;
103        boolean isEscaped = false;
104
105        for (int i = 0; i < commandLine.length(); i++) {
106            char c = commandLine.charAt(i);
107
108            if (c == '\\' && !isEscaped) {
109                isEscaped = true;
110                continue;
111            }
112
113            if (c == '\"' && !isEscaped) {
114                inQuotes = !inQuotes;
115            } else {
116                if (c == ' ' && !inQuotes) {
117                    if (currentArg.length() > 0) {
118                        args.add(currentArg.toString());
119                        currentArg.setLength(0);
120                    }
121                } else {
122                    currentArg.append(c);
123                }
124            }
125
126            if (isEscaped) {
127                isEscaped = false;
128            }
129        }
130
131        if (currentArg.length() > 0) {
132            args.add(currentArg.toString());
133        }
134
135        return args;
136    }
137
138}