/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. 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 the jCoderZ.org Project 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 REGENTS 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 AND 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 org.jcoderz.commons.taskdefs; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.jcoderz.commons.util.IoUtil; import com.caucho.hessian.client.HessianProxyFactory; import com.luntsys.luntbuild.facades.BuildParams; import com.luntsys.luntbuild.facades.Constants; import com.luntsys.luntbuild.facades.ILuntbuild; import com.luntsys.luntbuild.facades.lb12.BuildFacade; import com.luntsys.luntbuild.facades.lb12.ScheduleFacade; /** * Ant task to trigger a build on the Luntbuild system and download results * afterwards. * * @author Albrecht Messner */ public class LuntBuildTask extends Task { /** * Schedule start policy: allows multiple schedules to be running * simultaneously. */ public static final String START_MULTIPLE = "startMultiple"; /** * Schedule start policy: skips execution if schedule is currently running. */ public static final String SKIP_IF_RUNNING = "skipIfRunning"; /** * Schedule start policy: fails this task if schedule is currently running. */ public static final String FAIL_IF_RUNNING = "failIfRunning"; private static final List POLICY_LIST = new ArrayList(); static { POLICY_LIST.add(START_MULTIPLE); POLICY_LIST.add(SKIP_IF_RUNNING); POLICY_LIST.add(FAIL_IF_RUNNING); } /** Wait time between kicking off schedule and start polling status * and between schedule termination and log retrieval. */ private static final int WAIT_PERIOD = 5000; /** Interval to poll build server. */ private static final int POLL_INTERVAL = 2000; private String mLuntUrl; private String mUserName; private String mPassword; private String mProjectName; private String mScheduleName; private String mStartPolicy = FAIL_IF_RUNNING; private boolean mWaitForSchedule = true; private String mToDir; private final List mArtifacts = new ArrayList(); private ILuntbuild mLuntServer; /** * @param luntUrl The luntUrl to set. */ public void setLuntUrl (String luntUrl) { mLuntUrl = luntUrl; } /** * @param userName The userName to set. */ public void setUserName (String userName) { mUserName = userName; } /** * @param password The password to set. */ public void setPassword (String password) { mPassword = password; } /** * @param projectName The projectName to set. */ public void setProjectName (String projectName) { mProjectName = projectName; } /** * @param scheduleName The scheduleName to set. */ public void setScheduleName (String scheduleName) { mScheduleName = scheduleName; } /** * @param startPolicy The startPolicy to set. */ public void setStartPolicy (String startPolicy) { if (!POLICY_LIST.contains(startPolicy)) { throw new BuildException("Invalid start policy " + startPolicy + ", must be one of " + POLICY_LIST); } mStartPolicy = startPolicy; } /** * @param waitForSchedule The waitForSchedule to set. */ public void setWaitForSchedule (boolean waitForSchedule) { mWaitForSchedule = waitForSchedule; } /** * @param toDir The toDir to set. */ public void setToDir (String toDir) { mToDir = toDir; } /** * Adds an artifact for retrieval. * @param artifact the artifact to add */ public void addArtifact (Artifact artifact) { mArtifacts.add(artifact); } /** * Execute this ant task. * @throws BuildException if a build error occurs */ public void execute () throws BuildException { checkParameters(); try { final ScheduleFacade schedule = getSchedule(); final boolean startSchedule; if (schedule.getStatus() == Constants.SCHEDULE_STATUS_RUNNING) { if (mStartPolicy.equals(START_MULTIPLE)) { startSchedule = true; } else if (mStartPolicy.equals(SKIP_IF_RUNNING)) { startSchedule = false; } else { throw new BuildException( "Can't start build because schedule is already running"); } } else { startSchedule = true; } if (startSchedule) { startSchedule(); } } catch (BuildException x) { throw x; } catch (Exception x) { throw new BuildException(x); } } private void startSchedule () throws InterruptedException, IOException { log("Starting build for " + mProjectName + "/" + mScheduleName + " on server " + mLuntUrl, Project.MSG_INFO); getLuntServer().triggerBuild(mProjectName, mScheduleName, getBuildParams()); if (mWaitForSchedule) { waitForSchedule(); } } private void waitForSchedule () throws InterruptedException, IOException { Thread.sleep(WAIT_PERIOD); log("Waiting for build " + getLuntServer().getLastBuild(mProjectName, mScheduleName).getVersion() + " to finish"); while (getSchedule().getStatus() == Constants.SCHEDULE_STATUS_RUNNING) { log("Schedule running", Project.MSG_VERBOSE); Thread.sleep(POLL_INTERVAL); } final int termStatus = getSchedule().getStatus(); switch (termStatus) { case Constants.SCHEDULE_STATUS_SUCCESS: log("LuntBuild schedule " + mProjectName + "/" + mScheduleName + " succeeded"); dumpLogFile(); retrieveArtifacts(); break; case Constants.SCHEDULE_STATUS_FAILED: log("LuntBuild schedule " + mProjectName + "/" + mScheduleName + " FAILED"); dumpLogFile(); throw new BuildException("LuntBuild schedule " + mProjectName + "/" + mScheduleName + " FAILED"); default: throw new BuildException("Unexpected status for schedule " + mProjectName + "/" + mScheduleName + ": " + termStatus); } } private ScheduleFacade getSchedule () throws MalformedURLException { return getLuntServer().getScheduleByName(mProjectName, mScheduleName); } /** * @throws IOException * */ private void retrieveArtifacts () throws IOException { final BuildFacade currentBuild = getLuntServer().getLastBuild(mProjectName, mScheduleName); final String buildLogUrl = currentBuild.getBuildLogUrl(); final String path = buildLogUrl.substring(0, buildLogUrl.lastIndexOf('/')); final String artifactsBaseUrl = path + "/artifacts/"; log("Artifacts base URL: " + artifactsBaseUrl, Project.MSG_VERBOSE); HttpURLConnection.setFollowRedirects(true); for (final Iterator it = mArtifacts.iterator(); it.hasNext(); ) { final String artifactName = ((Artifact) it.next()).getName(); final File outputFile = new File(new File(mToDir), artifactName); if (outputFile.exists()) { throw new BuildException("Output file " + outputFile + " already exists"); } final String artifactUrl = artifactsBaseUrl + artifactName; log("Retrieving artifact " + artifactName); log("Retrieving from URL: " + artifactUrl, Project.MSG_VERBOSE); log("Writing to file: " + mToDir + File.separator + outputFile, Project.MSG_VERBOSE); final HttpURLConnection con = (HttpURLConnection) new URL(artifactUrl).openConnection(); con.setDoOutput(true); con.addRequestProperty("Keep-alive", "false"); con.connect(); if (con.getResponseCode() != HttpURLConnection.HTTP_OK) { throw new BuildException("Failed while retrieving artifact " + artifactUrl + ": " + con.getResponseMessage()); } writeArtifactToFile(outputFile, con); } } private void writeArtifactToFile ( final File outputFile, final HttpURLConnection con) throws IOException, FileNotFoundException { InputStream artifactInput = null; OutputStream artifactOutput = null; try { artifactInput = con.getInputStream(); artifactOutput = new FileOutputStream(outputFile); IoUtil.copy(artifactInput, artifactOutput); } finally { IoUtil.close(artifactInput); IoUtil.close(artifactOutput); } } /** * @throws InterruptedException * @throws IOException * */ private void dumpLogFile () throws InterruptedException, IOException { Thread.sleep(WAIT_PERIOD); final BuildFacade currentBuild = getLuntServer().getLastBuild(mProjectName, mScheduleName); final String buildLogUrlHtml = currentBuild.getBuildLogUrl(); log("Build log URL (HTML format): " + buildLogUrlHtml, Project.MSG_VERBOSE); final String buildLogUrlTxt = buildLogUrlHtml.substring(0, buildLogUrlHtml.lastIndexOf(".html")) + ".txt"; log("Build log URL (Text format): " + buildLogUrlTxt, Project.MSG_VERBOSE); final URL buildLog = new URL(buildLogUrlTxt); final HttpURLConnection con = (HttpURLConnection) buildLog.openConnection(); log("Got HTTP code " + con.getResponseCode(), Project.MSG_VERBOSE); InputStream is = null; try { is = con.getInputStream(); final byte[] data = IoUtil.readFully(is); final String buildLogData = new String(data); log("===== START Build log =====", Project.MSG_INFO); log(buildLogData, Project.MSG_INFO); log("===== END Build log =====", Project.MSG_INFO); } finally { IoUtil.close(is); } } private BuildParams getBuildParams () { final BuildParams params = new BuildParams(); params.setBuildNecessaryCondition("always"); params.setBuildType(Constants.BUILD_TYPE_CLEAN); params.setLabelStrategy(Constants.LABEL_NONE); params.setNotifyStrategy(Constants.NOTIFY_NONE); params.setPostbuildStrategy(Constants.POSTBUILD_NONE); // params.setScheduleId() params.setTriggerDependencyStrategy( Constants.TRIGGER_NONE_DEPENDENT_SCHEDULES); return params; } /** * @param luntUrl */ private void checkNotNull (Object obj, String name) { if (obj == null) { throw new BuildException("Parameter " + name + " missing"); } } /** * */ private void checkParameters () { checkNotNull(mLuntUrl, "luntUrl"); checkNotNull(mUserName, "userName"); checkNotNull(mPassword, "password"); checkNotNull(mProjectName, "projectName"); checkNotNull(mScheduleName, "scheduleName"); if (mArtifacts.size() > 0) { if (mToDir == null) { throw new BuildException("'toDir' must be set if artifacts are set"); } else { AntTaskUtil.ensureDirectory(new File(mToDir)); } if (!mWaitForSchedule) { throw new BuildException( "Can't retrieve artifacts when waitForBuild == false"); } } } private ILuntbuild getLuntServer () throws MalformedURLException { if (mLuntServer == null) { final HessianProxyFactory factory = new HessianProxyFactory(); factory.setUser(mUserName); factory.setPassword(mPassword); mLuntServer = (ILuntbuild) factory.create(ILuntbuild.class, mLuntUrl); } return mLuntServer; } /** * Represents an artifact to retrieve. This class basically consists of a * name. */ public static final class Artifact { private String mName; /** * @param name The name to set. */ public void setName (String name) { mName = name; } /** * @return Returns the name. */ public String getName () { return mName; } } }