Package net.sourceforge.cruisecontrol.sourcecontrols

Source Code of net.sourceforge.cruisecontrol.sourcecontrols.SVN

/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2001, ThoughtWorks, Inc.
* 651 W Washington Ave. Suite 600
* Chicago, IL 60661 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*     + Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*
*     + Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*
*     + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
*       names of its contributors may be used to endorse or promote
*       products derived from this software without specific prior
*       written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************/
package net.sourceforge.cruisecontrol.sourcecontrols;

import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.Modification;
import net.sourceforge.cruisecontrol.SourceControl;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.StreamPumper;
import net.sourceforge.cruisecontrol.util.ValidationHelper;

import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

/**
* This class implements the SourceControl methods for a Subversion repository.
* The call to Subversion is assumed to work without any setup. This implies
* that either authentication data must be available or the login parameters are
* specified in the cc configuration file.
*
* Note: You can also observe for changes a Subversion repository that you have
*       not checked out locally.
*
* @see    <a href="http://subversion.tigris.org/">subversion.tigris.org</a>
* @author <a href="etienne.studer@canoo.com">Etienne Studer</a>
*/
public class SVN implements SourceControl {

    private static final Logger LOG = Logger.getLogger(SVN.class);

    /** Date format expected by Subversion */
    static final SimpleDateFormat SVN_DATE_FORMAT_IN =
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

    /** Date format returned by Subversion in XML output */
    static final SimpleDateFormat SVN_DATE_FORMAT_OUT =
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    /** Set the time zone of the formatters to GMT. */
    static {
        SVN_DATE_FORMAT_IN.setTimeZone(TimeZone.getTimeZone("GMT"));
        SVN_DATE_FORMAT_OUT.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private Hashtable properties = new Hashtable();
    private String property;
    private String propertyOnDelete;

    /** Configuration parameters */
    private String repositoryLocation;
    private String localWorkingCopy;
    private String userName;
    private String password;

    public Hashtable getProperties() {
        return properties;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    public void setPropertyOnDelete(String propertyOnDelete) {
        this.propertyOnDelete = propertyOnDelete;
    }

    /**
     * Sets the repository location to use when making calls to Subversion.
     *
     * @param repositoryLocation  String indicating the url to the Subversion
     *                            repository on which to find the log history.
     */
    public void setRepositoryLocation(String repositoryLocation) {
        this.repositoryLocation = repositoryLocation;
    }

    /**
     * Sets the local working copy to use when making calls to Subversion.
     *
     * @param localWorkingCopy  String indicating the relative or absolute path
     *                          to the local working copy of the Subversion
     *                          repository of which to find the log history.
     */
    public void setLocalWorkingCopy(String localWorkingCopy) {
        this.localWorkingCopy = localWorkingCopy;
    }

    /**
     * Sets the username for authentication.
     */
    public void setUsername(String userName) {
        this.userName = userName;
    }

    /**
     * Sets the password for authentication.
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * This method validates that at least the repository location or the local
     * working copy location has been specified.
     *
     * @throws CruiseControlException  Thrown when the repository location and
     *                                 the local working copy location are both
     *                                 null
     */
    public void validate() throws CruiseControlException {
        ValidationHelper.assertTrue(repositoryLocation != null || localWorkingCopy != null,
                "At least 'repositoryLocation'or 'localWorkingCopy' is a required attribute on the Subversion task ");

        if (localWorkingCopy != null) {
            File workingDir = new File(localWorkingCopy);
            ValidationHelper.assertTrue(workingDir.exists() && workingDir.isDirectory(),
                    "'localWorkingCopy' must be an existing directory. Was "
                    + workingDir.getAbsolutePath());
        }
    }

    /**
     * Returns a list of modifications detailing all the changes between
     * the last build and the latest revision in the repository.
     * @return the list of modifications, or an empty list if we failed
     * to retrieve the changes.
     */
    public List getModifications(Date lastBuild, Date now) {
        List modifications = new ArrayList();
        Commandline command;
        try {
            command = buildHistoryCommand(lastBuild, now);
        } catch (CruiseControlException e) {
            LOG.error("Error building history command", e);
            return modifications;
        }
        try {
            modifications = execHistoryCommand(command, lastBuild);
        } catch (Exception e) {
            LOG.error("Error executing svn log command " + command, e);
        }
        fillPropertiesIfNeeded(modifications);
        return modifications;
    }


    /**
     * Generates the command line for the svn log command.
     *
     * For example:
     *
     * 'svn log --non-interactive --xml -v -r {lastbuildTime}:headRevision repositoryLocation'
     */
    Commandline buildHistoryCommand(Date lastBuild, Date checkTime) throws CruiseControlException {
        Commandline command = new Commandline();
        command.setExecutable("svn");

        if (localWorkingCopy != null) {
            command.setWorkingDirectory(localWorkingCopy);
        }

        command.createArgument().setValue("log");
        command.createArgument().setValue("--non-interactive");
        command.createArgument().setValue("--xml");
        command.createArgument().setValue("-v");
        command.createArgument().setValue("-r");
        command.createArgument().setValue("{" + formatSVNDate(lastBuild) + "}" + ":"
                + "{" + formatSVNDate(checkTime) + "}");

        if (userName != null) {
            command.createArgument().setValue("--username");
            command.createArgument().setValue(userName);
        }
        if (password != null) {
            command.createArgument().setValue("--password");
            command.createArgument().setValue(password);
        }
        if (repositoryLocation != null) {
            command.createArgument().setValue(repositoryLocation);
        }

        LOG.debug("Executing command: " + command);

        return command;
    }


    static String formatSVNDate(Date lastBuild) {
        return SVN_DATE_FORMAT_IN.format(lastBuild);
    }


    private List execHistoryCommand(Commandline command, Date lastBuild)
        throws InterruptedException, IOException, ParseException, JDOMException {

        Process p = command.execute();

        logErrorStream(p);
        InputStream svnStream = p.getInputStream();
        List modifications = parseStream(svnStream, lastBuild);

        p.waitFor();
        p.getInputStream().close();
        p.getOutputStream().close();
        p.getErrorStream().close();

        return modifications;
    }

    private void logErrorStream(Process p) {
        StreamPumper errorPumper =
            new StreamPumper(p.getErrorStream(), new PrintWriter(System.err, true));
        new Thread(errorPumper).start();
    }

    private List parseStream(InputStream svnStream, Date lastBuild)
        throws JDOMException, IOException, ParseException, UnsupportedEncodingException {

        InputStreamReader reader = new InputStreamReader(svnStream, "UTF-8");
        return SVNLogXMLParser.parseAndFilter(reader, lastBuild);
    }

    void fillPropertiesIfNeeded(List modifications) {
        if (property != null) {
            if (modifications.size() > 0) {
                properties.put(property, "true");
            }
        }

        if (propertyOnDelete != null) {
            for (int i = 0; i < modifications.size(); i++) {
                Modification modification = (Modification) modifications.get(i);
                Modification.ModifiedFile file = (Modification.ModifiedFile) modification.files.get(0);
                if (file.action.equals("deleted")) {
                    properties.put(propertyOnDelete, "true");
                    break;
                }
            }
        }
    }

    static final class SVNLogXMLParser {

        private SVNLogXMLParser() {
        }


        static List parseAndFilter(Reader reader, Date lastBuild)
                throws ParseException, JDOMException, IOException {

            Modification[] modifications = parse(reader);
            return filterModifications(modifications, lastBuild);
        }


        static Modification[] parse(Reader reader)
                throws ParseException, JDOMException, IOException {

            SAXBuilder builder = new SAXBuilder(false);
            Document document = builder.build(reader);
            return parseDOMTree(document);
        }

        static Modification[] parseDOMTree(Document document) throws ParseException {
            List modifications = new ArrayList();

            Element rootElement = document.getRootElement();
            List logEntries = rootElement.getChildren("logentry");
            for (Iterator iterator = logEntries.iterator(); iterator.hasNext();) {
                Element logEntry = (Element) iterator.next();

                Modification[] modificationsOfRevision = parseLogEntry(logEntry);
                modifications.addAll(Arrays.asList(modificationsOfRevision));
            }

            return (Modification[]) modifications.toArray(new Modification[modifications.size()]);
        }

        static Modification[] parseLogEntry(Element logEntry) throws ParseException {
            List modifications = new ArrayList();

            Element logEntryPaths = logEntry.getChild("paths");
            List paths = logEntryPaths.getChildren("path");
            for (Iterator iterator = paths.iterator(); iterator.hasNext();) {
                Element path = (Element) iterator.next();

                Modification modification = new Modification("svn");

                modification.modifiedTime = convertDate(logEntry.getChildText("date"));
                modification.userName = logEntry.getChildText("author");
                modification.comment = logEntry.getChildText("msg");
                modification.revision = logEntry.getAttributeValue("revision");

                Modification.ModifiedFile modfile = modification.createModifiedFile(path.getText(), null);
                modfile.action = convertAction(path.getAttributeValue("action"));
                modfile.revision = modification.revision;

                modifications.add(modification);
            }

            return (Modification[]) modifications.toArray(new Modification[modifications.size()]);
        }

        /**
         * Converts the specified SVN date string into a Date.
         * @param date with format "yyyy-MM-dd'T'HH:mm:ss.SSS" + "...Z"
         * @return
         * @throws ParseException if specified date doesn't match the expected format
         */
        static Date convertDate(String date) throws ParseException {
            final int zIndex = date.indexOf('Z');
            if (zIndex - 3 < 0) {
                throw new ParseException(date
                        + " doesn't match the expected subversion date format", date.length());
            }
            String withoutMicroSeconds = date.substring(0, zIndex - 3);
            return SVN_DATE_FORMAT_OUT.parse(withoutMicroSeconds);
        }

        static String convertAction(String action) {
            if (action.equals("A")) {
                return "added";
            }
            if (action.equals("M")) {
                return "modified";
            }
            if (action.equals("D")) {
                return "deleted";
            }
            return "unknown";
        }

        /**
         * Unlike CVS, Subversion maps dates to revisions which leads to a
         * different behavior when using the svn log command in conjunction with
         * dates, e.g., a date maps to a revision but the revision may have been
         * created earlier than the specified date. Therefore, if we are only
         * interested in changes that took place after the last build date, we
         * have to filter the modifications returned from the log command and
         * omit modifications that are older than the last build date.
         *
         * @see <a href="http://subversion.tigris.org/">subversion.tigris.org</a>
         */
        static List filterModifications(Modification[] modifications, Date lastBuild) {
            List filtered = new ArrayList();
            for (int i = 0; i < modifications.length; i++) {
                Modification modification = modifications[i];
                if (modification.modifiedTime.getTime() > lastBuild.getTime()) {
                    filtered.add(modification);
                }
            }
            return filtered;
        }
    }
}
TOP

Related Classes of net.sourceforge.cruisecontrol.sourcecontrols.SVN

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.