org.scilla.converter.ExternalConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.scilla.converter.ExternalConverter.java

Source

/*
 * scilla
 *
 * Copyright (C) 2001  R.W. van 't Veer
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

package org.scilla.converter;

import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.List;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.scilla.*;

/**
 * External converter class.  Takes converter configurations from
 * scilla configuration.
 * <DL>
 * <DT><CODE>converters.external.NAME.exec</CODE></DT>
 *     <DD>
 *         <CODE>exec</CODE> holds the executable name for this converter.
 *     </DD>
 * <DT><CODE>converters.external.NAME.format</CODE></DT>
 *     <DD>
 *         <CODE>format</CODE> holds the commandline format for this
 *         command.  Typically something like <CODE>sio</CODE>.
 *         <CODE>s</CODE> stands for switches block, <CODE>i</CODE> input
 *         file (including optional input switches) and <CODE>o</CODE>
 *         output file (same as <CODE>i</CODE>).
 *     </DD>
 * <DT><CODE>converters.external.NAME.silent_switch</CODE></DT>
 *     <DD>
 *         <EM>Optional</EM> switch to suppress progress info, like
 *         <CODE>-q</CODE>.
 *     </DD>
 * <DT><CODE>converters.external.NAME.ignore_exitstat</CODE></DT>
 *     <DD>
 *         <EM>Optional</EM>, ignore process exit status.
 *     </DD>
 * <DT><CODE>converters.external.NAME.cd_to_output_dir</CODE></DT>
 *     <DD>
 *         <EM>Optional</EM>, make converter change directory to
 *         output directory and add output file to command
 *         without output directory.
 *     </DD>
 * <DT><CODE>converters.external.NAME.inputtypes</CODE></DT>
 *     <DD>
 *         Spaces separated allowed input mime-types list.
 *     </DD>
 * <DT><CODE>converters.external.NAME.outputtypes</CODE></DT>
 *     <DD>
 *         Spaces separated allowed output mime-types list.
 *     </DD>
 * <DT><CODE>converters.external.NAME.inputswitch.TYPE</CODE></DT>
 *     <DD>
 *         <EM>Optional</EM> switch prepended to input filename.
 *         The <CODE>TYPE</CODE> part is optional, it can be set to
 *         the mime-type base (audio, video etc.) or a full
 *         mime-type identifier.
 *     </DD>
 * <DT><CODE>converters.external.NAME.outputswitch.TYPE</CODE></DT>
 *     <DD>
 *         <EM>Optional</EM> switch prepended to output filename.
 *         The <CODE>TYPE</CODE> part is optional, it can be set to
 *         the mime-type base (audio, video etc.) or a full
 *         mime-type identifier.
 *     </DD>
 * <DT><CODE>converters.external.NAME.switch.string.PARAM</CODE></DT>
 *     <DD>
 *         Defines a switch translation.  The given
 *         <CODE>PARAM</CODE> will be substituted by the set value
 *         to build a commandline.
 *     </DD>
 * <DT><CODE>converters.external.NAME.switch.number.PARAM</CODE></DT>
 *     <DD>
 *         Defines a switch translation.  The given
 *         <CODE>PARAM</CODE> will be substituted by the set value
 *         to build a commandline.
 *     </DD>
 * <DT><CODE>converters.external.NAME.switch.boolean.PARAM</CODE></DT>
 *     <DD>
 *         Defines a switch translation.  The given
 *         <CODE>PARAM</CODE> will be substituted by the set value
 *         to build a commandline.  The request parameter value will
 *         be discarded.
 *     </DD>
 * </DL>
 * @see org.scilla.Config
 * @author R.W. van 't Veer
 * @version $Revision: 1.21 $
 */
public class ExternalConverter implements Converter {
    private static final Log log = LogFactory.getLog(ExternalConverter.class);
    private static final Config config = Config.getInstance();

    /** config key prefix for converter definitions */
    public static final String CONVERTER_PREFIX = "converters.external";
    /** config key delimiter */
    public static final char keyDelimiter = '.';

    private Request request = null;
    private String outputFile = null;
    private String converterName = null;

    private volatile String errorMessage = null;
    private volatile int exitValue = 0; // 0 means success
    private volatile boolean finished = false;
    private volatile boolean started = false;

    private static List reservedParameters = new Vector();
    static {
        reservedParameters.add(Request.OUTPUT_TYPE_PARAMETER);
    }
    private static Map inputTypeMap = new HashMap();
    private static Map outputTypeMap = new HashMap();
    private static Map parameterMap = new HashMap();
    private static Map execMap = new HashMap();
    private static Map formatMap = new HashMap();
    private static Map inputSwitchMap = new HashMap();
    private static Map outputSwitchMap = new HashMap();
    private static Map silentSwitchMap = new HashMap();
    private static Map stringSwitchMap = new HashMap();
    private static Map numberSwitchMap = new HashMap();
    private static Map booleanSwitchMap = new HashMap();
    private static Set ignoreExitstatSet = new HashSet();
    private static Set cd2outputDirSet = new HashSet();
    private static Set blacklistSet = new HashSet();
    static {
        for (Iterator it = config.keySet().iterator(); it.hasNext();) {
            String key = (String) it.next();
            if (key.startsWith(CONVERTER_PREFIX)) {
                String tail = key.substring(CONVERTER_PREFIX.length() + 1);
                StringTokenizer keyst = new StringTokenizer(tail, "" + keyDelimiter);
                String name = keyst.nextToken();
                String type = keyst.nextToken();
                String val = config.getString(key);
                if (type.equals("exec")) {
                    execMap.put(name, val);

                    // blacklist it if executable not available
                    if (!canExecute(val)) {
                        blacklistSet.add(name);
                    }
                } else if (type.equals("format")) {
                    formatMap.put(name, val);
                } else if (type.equals("ignore_exitstat")) {
                    if (config.getBoolean(key))
                        ignoreExitstatSet.add(name);
                } else if (type.equals("cd_to_output_dir")) {
                    if (config.getBoolean(key))
                        cd2outputDirSet.add(name);
                } else if (type.equals("silent_switch")) {
                    silentSwitchMap.put(name, val);
                } else if (type.equals("inputtypes")) {
                    StringTokenizer st = new StringTokenizer(val);
                    while (st.hasMoreTokens()) {
                        String mtype = st.nextToken();
                        Set s = (Set) inputTypeMap.get(mtype);
                        if (s == null) {
                            s = new HashSet();
                            inputTypeMap.put(mtype, s);
                        }
                        s.add(name);
                    }
                } else if (type.equals("outputtypes")) {
                    StringTokenizer st = new StringTokenizer(val);
                    while (st.hasMoreTokens()) {
                        String mtype = st.nextToken();
                        Set s = (Set) outputTypeMap.get(mtype);
                        if (s == null) {
                            s = new HashSet();
                            outputTypeMap.put(mtype, s);
                        }
                        s.add(name);
                    }
                } else if (type.equals("inputswitch")) {
                    if (keyst.hasMoreTokens()) {
                        String mtype = keyst.nextToken();
                        inputSwitchMap.put(name + keyDelimiter + mtype, val);
                    } else {
                        inputSwitchMap.put(name, val);
                    }
                } else if (type.equals("outputswitch")) {
                    if (keyst.hasMoreTokens()) {
                        String mtype = keyst.nextToken();
                        outputSwitchMap.put(name + keyDelimiter + mtype, val);
                    } else {
                        outputSwitchMap.put(name, val);
                    }
                } else if (type.equals("switch")) {
                    String stype = keyst.nextToken();
                    String sname = keyst.nextToken();

                    // for can convert
                    Set s = (Set) parameterMap.get(sname);
                    if (s == null) {
                        s = new HashSet();
                        parameterMap.put(sname, s);
                    }
                    s.add(name);

                    // for commandline building
                    if (stype.equals("string")) {
                        Map h = (Map) stringSwitchMap.get(name);
                        if (h == null) {
                            h = new HashMap();
                            stringSwitchMap.put(name, h);
                        }
                        h.put(sname, val);
                    } else if (stype.equals("number")) {
                        Map h = (Map) numberSwitchMap.get(name);
                        if (h == null) {
                            h = new HashMap();
                            numberSwitchMap.put(name, h);
                        }
                        h.put(sname, val);
                    } else if (stype.equals("boolean")) {
                        Map h = (Map) booleanSwitchMap.get(name);
                        if (h == null) {
                            h = new HashMap();
                            booleanSwitchMap.put(name, h);
                        }
                        h.put(sname, val);
                    }
                }
            }
        }

        if (blacklistSet.size() > 0) {
            log.warn("excutable(s) do(es) not exist for: " + blacklistSet);
        }
    }

    public void convert() {
        started = true;
        _convert();
        finished = true;
    }

    private void _convert() {
        // build commandline
        List cmdline = new Vector();
        cmdline.add(execMap.get(converterName));

        // handle commandline format
        final String format = (String) formatMap.get(converterName);
        final int formatLen = format.length();
        for (int i = 0; i < formatLen; i++) {
            switch (format.charAt(i)) {
            case 'i':
                // add input file, optionally with switch
                if (inputSwitchMap.containsKey(converterName)) {
                    cmdline.add(inputSwitchMap.get(converterName));
                } else {
                    String mtype = request.getInputType();
                    String btype = mtype.substring(0, mtype.indexOf('/'));
                    mtype = converterName + keyDelimiter + mtype;
                    btype = converterName + keyDelimiter + btype;
                    if (inputSwitchMap.containsKey(mtype)) {
                        cmdline.add(inputSwitchMap.get(mtype));
                    } else if (inputSwitchMap.containsKey(btype)) {
                        cmdline.add(inputSwitchMap.get(btype));
                    }
                }
                cmdline.add(request.getInputFile());
                break;
            case 'o':
                // add output file, optionally with switch
                if (outputSwitchMap.containsKey(converterName)) {
                    cmdline.add(outputSwitchMap.get(converterName));
                } else {
                    String mtype = request.getOutputType();
                    String btype = mtype.substring(0, mtype.indexOf('/'));
                    mtype = converterName + keyDelimiter + mtype;
                    btype = converterName + keyDelimiter + btype;
                    if (outputSwitchMap.containsKey(mtype)) {
                        cmdline.add(outputSwitchMap.get(mtype));
                    } else if (outputSwitchMap.containsKey(btype)) {
                        cmdline.add(outputSwitchMap.get(btype));
                    }
                }
                if (cd2outputDirSet.contains(converterName)) {
                    String fn = outputFile.substring(outputFile.lastIndexOf(File.separator) + 1);
                    cmdline.add(fn);
                } else {
                    cmdline.add(outputFile);
                }
                break;
            case 's':
                // add silent switch
                if (silentSwitchMap.containsKey(converterName)) {
                    cmdline.add(silentSwitchMap.get(converterName));
                }

                // add switches to commandline
                Map sm = (Map) stringSwitchMap.get(converterName);
                Map nm = (Map) numberSwitchMap.get(converterName);
                Map bm = (Map) booleanSwitchMap.get(converterName);
                Iterator it = request.getParameters().iterator();
                while (it.hasNext()) {
                    RequestParameter rp = (RequestParameter) it.next();
                    if (reservedParameters.indexOf(rp.key) == -1) {
                        // string
                        if (sm != null && sm.containsKey(rp.key)) {
                            cmdline.add(sm.get(rp.key));
                            cmdline.add(rp.val);
                        }
                        // number
                        else if (nm != null && nm.containsKey(rp.key)) {
                            cmdline.add(nm.get(rp.key));
                            cmdline.add(rp.val);
                        }
                        // boolean
                        else if (bm != null && bm.containsKey(rp.key)) {
                            cmdline.add(bm.get(rp.key));
                        }
                    }
                }
                break;
            default:
                // TODO throw exception
                break;
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("cmdline: " + cmdline);
        }

        // prepare command
        String[] cmd = (String[]) cmdline.toArray(new String[0]);
        File dir = null;
        if (cd2outputDirSet.contains(converterName)) {
            String dn = outputFile.substring(0, outputFile.lastIndexOf(File.separator));
            dir = new File(dn);
        }

        // run system command
        QueuedProcess proc = null;
        try {
            proc = new QueuedProcess(cmd, null, dir);
            // wait for command to finish
            exitValue = proc.exitValue();
        } catch (Exception e) {
            errorMessage = e.getMessage();
        }
        if (proc != null) {
            errorMessage = proc.getErrorLog();
        }
    }

    public boolean canConvert(Request req) {
        Set conv = new HashSet();
        Collection c;
        Iterator it;

        // can handle input?
        c = (Collection) inputTypeMap.get(req.getInputType());
        if (c == null) {
            return false;
        }
        conv.addAll(c);
        // TODO remove all references to blacklisted converters at setup
        conv.removeAll(blacklistSet);
        if (conv.size() == 0) {
            return false;
        }

        // can handle output?
        c = (Collection) outputTypeMap.get(req.getOutputType());
        if (c == null) {
            return false;
        }
        conv.retainAll(c);
        if (conv.size() == 0) {
            return false;
        }

        // supports all parameters?
        it = req.getParameterKeys().iterator();
        while (it.hasNext()) {
            String key = (String) it.next();
            if (reservedParameters.indexOf(key) == -1) {
                c = (Collection) parameterMap.get(key);
                if (c == null) {
                    return false;
                }
                conv.retainAll(c);
                if (conv.size() == 0) {
                    return false;
                }
            }
        }

        // determine which is "best"
        // TODO add weight parameter
        it = conv.iterator();
        converterName = (String) it.next();

        log.debug("appropriate converter: " + converterName);
        return true;
    }

    public void setRequest(Request req) {
        request = req;
    }

    public boolean exitSuccess() {
        if (!finished) {
            throw new IllegalStateException();
        }
        return ignoreExitstatSet.contains(converterName) || (exitValue == 0);
    }

    public String getErrorMessage() {
        if (!finished) {
            throw new IllegalStateException();
        }
        return errorMessage;
    }

    public void setOutputFile(String fn) {
        if (started) {
            throw new IllegalStateException();
        }
        outputFile = fn;
    }

    public String getOutputFile() {
        return outputFile;
    }

    public boolean hasFinished() {
        return finished;
    }

    /**
     * Determine is a OS command can be executed.
     * @param exec the name of the executable
     * @return <tt>true</tt> if the command can be executed
     */
    private static boolean canExecute(String exec) {
        // TODO implement me!
        return true;
    }
}