/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2007, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 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.servlet; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import net.sourceforge.cruisecontrol.BuildInfo; import net.sourceforge.cruisecontrol.LogFileReader; import net.sourceforge.cruisecontrol.ProjectState; import net.sourceforge.cruisecontrol.StatusHelper; /** * Servlet to generate an XML file in the format required by cctray * see http://confluence.public.thoughtworks.org/display/CCNET/CCTray * */ public class XmlServlet extends HttpServlet { private static final int MILLISECONDS_IN_SECOND = 1000; public static final String STATUS_UNKNOWN = "Unknown"; public static final String STATUS_SUCCESS = "Success"; public static final String STATUS_FAILURE = "Failure"; public static final String ACTIVITY_CHECKING_MODIFICATIONS = "CheckingModifications"; public static final String ACTIVITY_BUILDING = "Building"; public static final String ACTIVITY_SLEEPING = "Sleeping"; public static final String LOG_DIR = "logDir"; public static final String SINGLE_PROJECT = "singleProject"; public static final String CURRENT_BUILD_STATUS_FILE = "currentBuildStatusFile"; private static final SimpleDateFormat XML_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); private String statusFile; private String singleProject; private String logDirPath; private File logDir; /** * Set up the servlet, mainly getting the required parameters. * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig) */ public void init(ServletConfig servletconfig) throws ServletException { super.init(servletconfig); this.statusFile = getInitParameter(servletconfig, CURRENT_BUILD_STATUS_FILE); this.singleProject = getInitParameter(servletconfig, SINGLE_PROJECT); this.logDirPath = getInitParameter(servletconfig, LOG_DIR); this.logDir = new File(logDirPath); } /** * Produce XML in the format required by CCTray * @see javax.servlet.http.HttpServlet */ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String[] projectDirs = getProjectDirs(); response.setContentType("text/xml"); PrintWriter writer = response.getWriter(); Element projectsElement = new Element("Projects"); for (int i = 0; i < projectDirs.length; i++) { String project = projectDirs[i]; File projectDir = new File(this.logDir, project); if (!projectDir.isDirectory()) { throw new ServletException("Project directory must be a directory " + this.logDirPath + File.separator + project); } // initialise status Helper for this project StatusHelper statusHelper = new StatusHelper(); statusHelper.setProjectDirectory(projectDir); Element projectElement = new Element("Project"); projectElement.setAttribute("name", project); projectElement.setAttribute("activity", getActivity(project, statusHelper)); projectElement.setAttribute("lastBuildStatus", getFormattedStatus(statusHelper)); projectElement.setAttribute("lastBuildLabel", statusHelper.getLastSuccessfulBuildLabel()); projectElement.setAttribute("lastBuildTime", getFormattedDate(statusHelper)); projectElement.setAttribute("nextBuildTime", getNextBuildDate(statusHelper)); projectElement.setAttribute("webUrl", getBaseUrl(request) + project); projectsElement.addContent(projectElement); } Document document = new Document(projectsElement); XMLOutputter outputter = new XMLOutputter(); outputter.setFormat(Format.getPrettyFormat()); outputter.output(document, writer); writer.flush(); writer.close(); } /** * Get the next build date for the project. * This is the date/time cruisecontrol will next check for modifications. * This assumes that the next build will be a whole number of intervals * from the las build date. * This is returned as a string in the format yyyy-MM-dd'T'HH:mm:ss * * @return the next build date */ private String getNextBuildDate(StatusHelper statusHelper) { int buildInterval = getBuildInterval(statusHelper); Date lastBuildDate = statusHelper.getLastBuildTime(); Calendar nextBuild = Calendar.getInstance(); if (buildInterval != 0) { final Date now = new Date(); int secondsFromLastBuildCheck = (int) (((now.getTime() - lastBuildDate.getTime()) / MILLISECONDS_IN_SECOND) % buildInterval); int secondsFromNow = buildInterval - secondsFromLastBuildCheck; nextBuild.roll(Calendar.SECOND, secondsFromNow); } Date nextBuildDate = nextBuild.getTime(); return XML_DATE_FORMAT.format(nextBuildDate); } /** * Get the build interval from the log file. * * @return the build interval in Seconds */ private int getBuildInterval(StatusHelper statusHelper) { int buildInterval = 0; try { BuildInfo lastBuild = statusHelper.getLastBuild(); if (lastBuild != null) { LogFileReader reader = lastBuild.getLogFile().getReader(); buildInterval = reader.getBuildInterval(); } } catch (JDOMException e) { // TODO log this error - non-fatal, we just can't calculate the next build time } catch (IOException e) { // TODO log this error - non-fatal, we just can't calculate the next build time } return buildInterval; } /** * Get an init parameter, first trying servletConfig, then trying servletContext. * A ServletException exception is thrown if the paramter is not found. * @param servletconfig the config to use * @param name the name of the parameter to return * @return the value of the parameter * @throws ServletException exception thrown if parameter not found */ private String getInitParameter(ServletConfig servletconfig, String name) throws ServletException { String value = servletconfig.getInitParameter(name); if (value == null) { value = servletconfig.getServletContext().getInitParameter(name); } getInitParameter("test"); if (value == null) { throw new ServletException("Context parameter " + name + " needs to be set."); } return value; } /** * Get the time of the last build * @return the time of the last build */ private String getFormattedDate(StatusHelper statusHelper) { Date date = statusHelper.getLastBuildTime(); return XML_DATE_FORMAT.format(date); } /** * Get the current activity. */ private String getActivity(String project, StatusHelper statusHelper) { String htmlFormattedActivity = statusHelper.getCurrentStatus( singleProject, logDirPath, project, statusFile); return getXmlActivity(htmlFormattedActivity); } /** * Get the current build status. * @return the build status */ private String getFormattedStatus(StatusHelper statusHelper) { final String result = statusHelper.getLastBuildResult(); return getXmlStatus(result); } /** * Get a list of project names * @return an array containing the full paths to the project directories * @throws ServletException */ private String[] getProjectDirs() throws ServletException { if (this.logDir == null) { throw new ServletException("Log file directory not found"); } if (!this.logDir.isDirectory()) { throw new ServletException("Could not access log directory"); } final String[] projectDirs = logDir.list(new FilenameFilter() { public boolean accept(File dir, String name) { return (new File(dir, name).isDirectory()); } }); Arrays.sort(projectDirs); return projectDirs; } /** * Get the base URL for this cruise control instance * @param request the request * @return the base URL */ private String getBaseUrl(HttpServletRequest request) { String baseURL = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/buildresults/"; return baseURL; } /** * Get the activity in the format required by cctray. * This will be one of * <ul> * <li>Sleeping</li> * <li>Building</li> * <li>CheckingModifications</li> * </ul> * @param project the project name * @return the build status * @param activity the activity from {@link ProjectState} * @return the activity */ String getXmlActivity(String activity) { String xmlActivity = ACTIVITY_SLEEPING; if (activity.startsWith(ProjectState.BUILDING.getDescription())) { xmlActivity = ACTIVITY_BUILDING; } else if (activity.startsWith(ProjectState.BOOTSTRAPPING.getDescription()) || activity.startsWith(ProjectState.MODIFICATIONSET.getDescription()) || activity.startsWith(ProjectState.MERGING_LOGS.getDescription()) || activity.startsWith(ProjectState.PUBLISHING.getDescription())) { xmlActivity = ACTIVITY_CHECKING_MODIFICATIONS; } return xmlActivity; } /** * Get the status required by cctray * This will be one of * <ul> * <li>Unknown</li> * <li>Success</li> * <li>Failure</li> * @param status the status from {@link StatusHelper} * @return the status */ String getXmlStatus(final String status) { String xmlStatus = STATUS_UNKNOWN; if (StatusHelper.PASSED.equalsIgnoreCase(status)) { xmlStatus = STATUS_SUCCESS; } if (StatusHelper.FAILED.equalsIgnoreCase(status)) { xmlStatus = STATUS_FAILURE; } return xmlStatus; } public String getLogDirPath() { return logDirPath; } public String getSingleProject() { return singleProject; } public String getStatusFile() { return statusFile; } }