/*
 * Copyright 2002,2004 The Apache Software Foundation.
 * 
 * Licensed 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 org.apache.axis.tools.ant.axis;

import org.apache.axis.AxisFault;
import org.apache.axis.client.AdminClient;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;

import javax.xml.rpc.ServiceException;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Task to administer a local or remote Axis server. Remember, for remote admin,
 * the server has to be accept remote management calls.
 *
 * @ant.task category="axis" name="axis-admin"
 */
public class AdminClientTask extends MatchingTask {

    /**
     * flag to control action on execution trouble
     */
    private boolean failOnError = true;
    private String hostname;
    private int port = 0;
    private String servletPath;
    private File srcDir = null;
    private File xmlFile;
    private String transportChain;
    private String username;
    private String password;
    private String fileProtocol;
    private String action = "";
    private String url;
    private boolean debug;
    private String newPassword;
    private LinkedList argslist;
    private boolean fork = false;
    private Path classpath = null;

    /**
     * set a debug flag
     *
     * @param debug
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * set a new password; only valid if action=passwd
     *
     * @param newPassword
     */
    public void setNewPassword(String newPassword) {
        this.newPassword = newPassword;
    }

    /**
     * full url to the admin endpoint
     *
     * @param url
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * specifies that a simple file protocol be used
     *
     * @param fileProtocol
     */
    public void setFileProtocol(String fileProtocol) {
        this.fileProtocol = fileProtocol;
    }

    /**
     * name the host to admin
     *
     * @param hostname
     */
    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    /**
     * the admin password
     *
     * @param password
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * the port to connect to
     *
     * @param port
     */
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * the path to the AxisAdmin servlet
     *
     * @param servletPath
     */
    public void setServletPath(String servletPath) {
        this.servletPath = servletPath;
    }

    /**
     * Set the source dir to find the source text files.
     */
    public void setSrcdir(File srcDir) {
        this.srcDir = srcDir;
    }

    /**
     * the name of the XML file containing deployment information
     *
     * @param xmlFile
     */
    public void setXmlFile(File xmlFile) {
        this.xmlFile = xmlFile;
    }

    /**
     * set the transport chain to use
     *
     * @param transportChain
     */
    public void setTransportChain(String transportChain) {
        this.transportChain = transportChain;
    }

    /**
     * username to log in as
     *
     * @param username
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * Whether or not the build should halt if this task fails.
     * Defaults to <code>true</code>.
     */
    public void setFailOnError(boolean fail) {
        failOnError = fail;
    }

    /**
     * If true, forks the ant invocation.
     *
     * @param f "true|false|on|off|yes|no"
     */
    public void setFork(boolean f) {
        fork = f;
    }

    /**
     * validation code
     *
     * @throws org.apache.tools.ant.BuildException
     *          if validation failed
     */
    protected void validate()
            throws BuildException {
        if (srcDir != null) {
            if (!srcDir.exists()) {
                throw new BuildException("srcdir does not exist!");
            }
            if (!srcDir.isDirectory()) {
                throw new BuildException("srcdir is not a directory!");
            }
        }
    }

    /**
     * trace out parameters
     *
     * @param logLevel to log at
     * @see org.apache.tools.ant.Project#log
     */
    public void traceParams(int logLevel) {
        log("Running axis-admin with parameters:", logLevel);
        log("  action:" + action, logLevel);
        log("  url:" + url, logLevel);
        log("  hostname:" + hostname, logLevel);
        log("  port:" + port, logLevel);
        log("  servletPath:" + servletPath, logLevel);
        log("  fileProtocol:" + fileProtocol, logLevel);
        log("  username:" + username, logLevel);
        log("  password:" + password, logLevel);
        log("  transportChain:" + transportChain, logLevel);
        log("  debug:" + debug, logLevel);
    }

    /**
     * <p>Processes a set of administration commands.</p>
     * <p>The following commands are available:</p>
     * <ul>
     * <li><code>-l<i>url</i></code> sets the AxisServlet URL</li>
     * <li><code>-h<i>hostName</i></code> sets the AxisServlet host</li>
     * <li><code>-p<i>portNumber</i></code> sets the AxisServlet port</li>
     * <li><code>-s<i>servletPath</i></code> sets the path to the
     * AxisServlet</li>
     * <li><code>-f<i>fileName</i></code> specifies that a simple file
     * protocol should be used</li>
     * <li><code>-u<i>username</i></code> sets the username</li>
     * <li><code>-w<i>password</i></code> sets the password</li>
     * <li><code>-d</code> sets the debug flag (for instance, -ddd would
     * set it to 3)</li>
     * <li><code>-t<i>name</i></code> sets the transport chain touse</li>
     * <li><code>list</code> will list the currently deployed services</li>
     * <li><code>quit</code> will quit (???)</li>
     * <li><code>passwd <i>value</i></code> changes the admin password</li>
     * <li><code><i>xmlConfigFile</i></code> deploys or undeploys
     * Axis components and web services</li>
     * </ul>
     * <p>If <code>-l</code> or <code>-h -p -s</code> are not set, the
     * AdminClient will invoke
     * <code>http://localhost:8080/axis/servlet/AxisServlet</code>.</p>
     * <p/>
     * outputs XML result or null in case of failure. In the case of multiple
     * commands, the XML results will be concatenated, separated by \n
     *
     * @throws BuildException something went wrong
     */
    public void execute() throws BuildException {
        traceParams(Project.MSG_VERBOSE);
        validate();
        argslist = new LinkedList();

        //build an array of args
        addArgs("-l", url, url != null);
        addArgs("-h", hostname, hostname != null);
        addArgs("-p", Integer.toString(port), port != 0);
        addArgs("-s", servletPath, servletPath != null);
        addArgs("-f", fileProtocol, fileProtocol != null);
        addArgs("-u", username, username != null);
        addArgs("-w", password, password != null);
        addArgs("-t", transportChain, transportChain != null);
        addArg("-d", debug);
        //action
        addArg(action, action != null);
        //action extras
        if ("passwd".equals(action)) {
            if (newPassword == null) {
                throw new BuildException("No newpassword set for passwd");
            }
            addArg(newPassword);
        } else {
            if (newPassword != null) {
                throw new BuildException(
                        "newpassword is only used when action=passwd");
            }
        }

        //final param is the xml file(s)
        if (xmlFile != null) {
            if (!xmlFile.exists()) {
                throw new BuildException("File " + xmlFile + " no found");
            }
            addArg(xmlFile.toString());
        }
        if (srcDir != null) {
            DirectoryScanner ds = super.getDirectoryScanner(srcDir);
            String[] files = ds.getIncludedFiles();
            for (int i = 0; i < files.length; i++) {
                File srcFile = new File(srcDir, files[i]);
                if (!srcFile.exists()) {
                    throw new BuildException("File " + srcFile + " no found");
                }
                addArg(srcFile.getAbsolutePath());
            }
        }
        
        //turn the list into an array
        int counter = 0;
        String[] args = new String[argslist.size()];
        Iterator it = argslist.iterator();
        while (it.hasNext()) {
            String arg = (String) it.next();
            args[counter] = arg;
            counter++;
        }
        if (fork) {
            executeInForkedVM(args);
        } else {
            executeInCurrentVM(args);
        }
    }

    private void executeInForkedVM(String[] args) {
        try {
            // Create an instance of the compiler, redirecting output to
            // the project log
            Java java = (Java) (getProject().createTask("java"));
            getProject().log("using classpath: " + classpath,
                    Project.MSG_DEBUG);
            java.setClasspath(classpath);
            java.setClassname("org.apache.axis.client.AdminClient");
            for (int i = 0; i < args.length; i++) {
                java.createArg().setValue(args[i]);
            }
            java.setFailonerror(failOnError);
            //we are forking here to be sure that if AdminClient calls
            //System.exit() it doesn't halt the build
            java.setFork(true);
            java.setTaskName("AdminClient");
            java.execute();
        } catch (BuildException e) {
            //rethrow these
            throw e;
        } catch (Exception e) {
            throw new BuildException("Exception in " + getTaskName(), e);
        }
    }

    private void executeInCurrentVM(String[] args) {
        //now create a client and invoke it
        AdminClient admin = null;
        try {
            admin = new AdminClient(true);
        } catch (ServiceException e) {
            throw new BuildException("failed to start the axis engine", e);
        }
        String result = null;
        try {
            result = admin.process(args);
            if (result != null) {
                log(result);
            } else {
                logOrThrow(getTaskName() + " got a null response");
            }
        } catch (AxisFault fault) {
            log(fault.dumpToString(), Project.MSG_ERR);
            traceParams(Project.MSG_ERR);
            logOrThrow(getTaskName()
                    + " failed with  "
                    + fault.getFaultCode().toString()
                    + " " + fault.getFaultString());
        } catch (BuildException e) {
            //rethrow these
            throw e;
        } catch (Exception e) {
            throw new BuildException("Exception in " + getTaskName(), e);
        }
    }

    private void logOrThrow(String text) throws BuildException {
        if (failOnError) {
            throw new BuildException(text);
        } else {
            log(text, Project.MSG_ERR);
        }
    }

    /**
     * add one arg
     *
     * @param argument
     */
    protected void addArg(String argument) {
        argslist.add(argument);
    }

    /**
     * add one arg
     *
     * @param argument
     */
    protected void addArg(String argument, boolean test) {
        if (test) {
            argslist.add(argument);
        }
    }

    /**
     * add an arg pair
     *
     * @param argument
     * @param param
     */
    protected void addArgs(String argument, String param) {
        addArg(argument);
        addArg(param);
    }

    /**
     * add an arg pair if the test is true
     *
     * @param argument first arg
     * @param param    param to accompany
     * @param test     test to trigger argument add
     */
    protected void addArgs(String argument, String param, boolean test) {
        if (test) {
            addArg(argument);
            addArg(param);
        }
    }

    /**
     * Set the optional classpath 
     *
     * @param classpath the classpath to use when loading class
     */
    public void setClasspath(Path classpath) {
        createClasspath().append(classpath);
    }

    /**
     * Set the optional classpath 
     *
     * @return a path instance to be configured by the Ant core.
     */
    public Path createClasspath() {
        if (classpath == null) {
            classpath = new Path(getProject());
        }
        return classpath.createPath();
    }

    /**
     * Set the reference to an optional classpath 
     *
     * @param r the id of the Ant path instance to act as the classpath
     */
    public void setClasspathRef(Reference r) {
        createClasspath().setRefid(r);
    }
}
