/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2001-2003, 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.jmx; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.management.InstanceNotFoundException; import javax.management.JMException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import net.sourceforge.cruisecontrol.BuildOutputLoggerManager; import net.sourceforge.cruisecontrol.CruiseControlException; import net.sourceforge.cruisecontrol.Modification; import net.sourceforge.cruisecontrol.ModificationSet; import net.sourceforge.cruisecontrol.Project; import net.sourceforge.cruisecontrol.SourceControl; import net.sourceforge.cruisecontrol.events.BuildProgressEvent; import net.sourceforge.cruisecontrol.events.BuildProgressListener; import net.sourceforge.cruisecontrol.events.BuildResultEvent; import net.sourceforge.cruisecontrol.events.BuildResultListener; import org.apache.log4j.Logger; /** * @author Niclas Olofsson * @author <a href="mailto:jcyip@thoughtworks.com">Jason Yip</a> */ public class ProjectController extends NotificationBroadcasterSupport implements ProjectControllerMBean, BuildProgressListener, BuildResultListener { public static final String OBJECT_NAME_PREFIX = "CruiseControl Project:name="; private static final Logger LOG = Logger.getLogger(ProjectController.class); private final Project project; private static int sequence = 0; private static final Object SEQUENCE_LOCK = new Object(); public ProjectController(final Project project) { this.project = project; project.addBuildProgressListener(this); project.addBuildResultListener(this); } private int nextSequence() { synchronized (SEQUENCE_LOCK) { return ++sequence; } } public void handleBuildProgress(final BuildProgressEvent event) { log("build progress event: " + event.getState().getDescription()); if (checkSourceProject(event.getProject())) { final Notification notification = new Notification("cruisecontrol.progress.event", this, nextSequence()); notification.setUserData(event.getState().getName()); sendNotification(notification); } } public void handleBuildResult(final BuildResultEvent event) { log("build result event: build " + String.valueOf(event.isBuildSuccessful() ? "successful" : "failed")); if (checkSourceProject(event.getProject())) { final Notification notification = new Notification("cruisecontrol.result.event", this, nextSequence()); notification.setUserData((event.isBuildSuccessful()) ? Boolean.TRUE : Boolean.FALSE); sendNotification(notification); } } private boolean checkSourceProject(final Project sourceProject) { boolean projectsMatch = false; if (project == sourceProject) { projectsMatch = true; } else { if (sourceProject == null) { LOG.warn("source project was null"); } else { LOG.warn("source project " + sourceProject.getName() + " didn't match internal project " + project.getName()); } } return projectsMatch; } public void pause() { log("pausing"); project.setPaused(true); } public void resume() { log("resuming"); project.setPaused(false); } public void build() { log("forcing build"); project.setBuildForced(true); } public void buildWithTarget(final String buildTarget) { log("forcing build with target \"" + buildTarget + "\""); project.forceBuildWithTarget(buildTarget); } public void buildWithTarget(String buildTarget, Map<String, String> addedProperties) { log("forcing build with target \"" + buildTarget + "\" with added Properties"); project.forceBuildWithTarget(buildTarget, addedProperties); } public void serialize() { log("serializing"); project.serializeProject(); } public boolean isPaused() { return project.isPaused(); } public void setLabel(final String label) { log("setting label to [" + label + "]"); project.setLabel(label); } public String getLabel() { return project.getLabel(); } public void setLastBuild(final String date) throws CruiseControlException { log("setting last build to [" + date + "]"); project.setLastBuild(date); } public String getLastBuild() { return project.getLastBuild(); } public boolean isLastBuildSuccessful() { return project.isLastBuildSuccessful(); } public void setLastSuccessfulBuild(final String date) throws CruiseControlException { log("setting last successful build to [" + date + "]"); project.setLastSuccessfulBuild(date); } public String getLastSuccessfulBuild() { return project.getLastSuccessfulBuild(); } public String getBuildStartTime() { final String buildStartTime = project.getBuildStartTime(); return buildStartTime == null ? "" : buildStartTime; } public void setLogDir(final String logdir) { log("setting log dir to [" + logdir + "]"); project.getLog().setDir(logdir); } public String getLogDir() { return project.getLogDir(); } public List<String> getLogLabels() { return project.getLogLabels(); } public String[] getLogLabelLines(final String logLabel, final int firstLine) { return project.getLogLabelLines(logLabel, firstLine); } public void setProjectName(final String name) { log("setting project name to [" + name + "]"); project.setName(name); } public String getProjectName() { return project.getName(); } public void setBuildInterval(long buildInterval) { log("setting build interval to [" + buildInterval + "]"); project.overrideBuildInterval(buildInterval); } public long getBuildInterval() { return project.getBuildInterval(); } public String getStatus() { return project.getStatusWithQueuePosition(); } private void log(final String message) { LOG.info(project.getName() + " Controller: " + message); } public void register(final MBeanServer server) throws JMException { final ObjectName projectName = new ObjectName(OBJECT_NAME_PREFIX + project.getName()); // Need to attempt to unregister the old mbean with the same name since // CruiseControlControllerJMXAdaptor keeps calling every time a change // is made to the config.xml file via JMX. try { server.unregisterMBean(projectName); } catch (InstanceNotFoundException noProblem) { } catch (MBeanRegistrationException noProblem) { } server.registerMBean(this, projectName); } /** * @return All the commit messages associated with the "current" modification set as * string[user name][commit message]. */ public String[][] commitMessages() { final ModificationSet modificationSet = project.getProjectConfig().getModificationSet(); final List<SourceControl> sourceControls = modificationSet.getSourceControls(); final Iterator<SourceControl> iterator = sourceControls.iterator(); final List<Modification> modifications = new ArrayList<Modification>(); while (iterator.hasNext()) { final SourceControl sourcecontrol = iterator.next(); modifications.addAll( sourcecontrol.getModifications(project.getLastBuildDate(), new Date()) ); } final String[][] commitMessages = new String[modifications.size()][]; for (int i = 0; i < modifications.size(); i++) { final Modification modification = modifications.get(i); commitMessages[i] = new String[2]; commitMessages[i][0] = modification.userName; commitMessages[i][1] = modification.comment; } return commitMessages; } /** * Output from the live output buffer, after line specified (inclusive). * @see net.sourceforge.cruisecontrol.util.BuildOutputLogger */ public String[] getBuildOutput(final Integer firstLine) { return BuildOutputLoggerManager.INSTANCE.lookup(getProjectName()).retrieveLines(firstLine); } /** * @return A unique (for this VM) identifying string for this logger instance. * This is intended to allow reporting apps (eg: Dashboard) to check if * the "live output" log file has been reset and to start asking for output from the first line * of the current output file if the logger has changed. * * Before the first call to retrieveLines(), the client should call getOutputLoggerID(), and hold that ID value. * If a client later calls retrieveLines() with a non-zero 'firstLine' parameter, and receives an empty array * as a result, that client should call getOutputLoggerID() again, and if the ID value differs, start reading * using a zero 'firstLine' parameter. * @see net.sourceforge.cruisecontrol.util.BuildOutputLogger#retrieveLines(int) */ public String getOutputLoggerID() { return BuildOutputLoggerManager.INSTANCE.lookup(getProjectName()).getID(); } }