org.xwiki.contrib.CommandRunnerScriptService.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.contrib.CommandRunnerScriptService.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.contrib;

import java.io.OutputStream;
import java.io.InputStream;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.io.IOUtils;
import org.xwiki.bridge.DocumentAccessBridge;
import org.xwiki.script.service.ScriptService;
import org.xwiki.component.annotation.Component;

@Component
@Named("commandRunner")
public class CommandRunnerScriptService implements ScriptService {
    @Inject
    private DocumentAccessBridge bridge;

    private static class CmdReturn {
        String stdout = "";
        String stderr = "";

        static final int retCode_TIMEOUT = (1 << 31);
        int retCode;
    }

    private static class Func {
        void call() throws Exception {
        }
    }

    private static Runnable startThread(final AtomicInteger ai, final Func f) {
        ai.incrementAndGet();
        Thread t = new Thread(new Runnable() {
            public void run() {
                try {
                    f.call();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    ai.decrementAndGet();
                }
            }
        });
        t.start();
        return t;
    }

    private static CmdReturn runCmd(final String cmd, final String stdin, long timeoutMillis) throws Exception {
        //System.out.println("debug: " + cmd + " < " + stdin);

        final CmdReturn out = new CmdReturn();
        final AtomicInteger ai = new AtomicInteger(0);
        final Process[] process = new Process[1];

        startThread(ai, new Func() {
            void call() throws Exception {
                process[0] = Runtime.getRuntime().exec(cmd);
                startThread(ai, new Func() {
                    void call() throws Exception {
                        out.stdout = IOUtils.toString(process[0].getInputStream(), "UTF-8");
                    }
                });
                startThread(ai, new Func() {
                    void call() throws Exception {
                        out.stderr = IOUtils.toString(process[0].getErrorStream(), "UTF-8");
                    }
                });
                startThread(ai, new Func() {
                    void call() throws Exception {
                        OutputStream stdinStream = process[0].getOutputStream();
                        IOUtils.write(stdin, stdinStream, "UTF-8");
                        stdinStream.close();
                    }
                });

                process[0].waitFor();
                if (out.retCode != CmdReturn.retCode_TIMEOUT) {
                    out.retCode = process[0].exitValue();
                }
            }
        });

        int waitMilliseconds = 0;
        while (ai.get() != 0) {
            Thread.sleep(10);
            waitMilliseconds += 10;
            if (waitMilliseconds > timeoutMillis) {
                if (process[0] != null) {
                    process[0].destroy();
                }
                //System.out.println("warning: TIMEOUT " + cmd + " < " + stdin);
                out.retCode = CmdReturn.retCode_TIMEOUT;
            }
            if (waitMilliseconds > (2 * timeoutMillis)) {
                throw new RuntimeException("hung thread");
            }
        }
        return out;
    }

    public Map<String, String> run(String command, String stdin, long timeoutMillis) throws Exception {
        Map<String, String> outMap = new HashMap<String, String>();
        if (!this.bridge.hasProgrammingRights()) {
            outMap.put("error", "ENOPERM");
            return outMap;
        }

        CmdReturn out = runCmd(command, stdin, timeoutMillis);
        outMap.put("stdout", out.stdout);
        outMap.put("stderr", out.stderr);
        if (out.retCode == CmdReturn.retCode_TIMEOUT) {
            outMap.put("error", "TIMEOUT");
            out.retCode = -1;
        } else {
            outMap.put("error", "none");
        }
        outMap.put("ret", "" + out.retCode);
        return outMap;
    }
}