/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2001-2003, 2006, 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; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.EventListener; import java.util.List; import java.util.Properties; import java.util.Set; import net.sourceforge.cruisecontrol.config.XMLConfigManager; import org.apache.log4j.Logger; /** * @author <a href="mailto:robertdw@users.sourceforge.net">Robert Watkins</a> */ public class CruiseControlController { private static final Logger LOG = Logger.getLogger(CruiseControlController.class); public static final String DEFAULT_CONFIG_FILE_NAME = "config.xml"; private File configFile; private final List<ProjectInterface> projects = new ArrayList<ProjectInterface>(); private final BuildQueue buildQueue = new BuildQueue(); private Properties versionProperties; private final List<Listener> listeners = new ArrayList<Listener>(); private XMLConfigManager configManager; private final ParsingConfigMutex parsingConfigMutex = new ParsingConfigMutex(); public CruiseControlController() { buildQueue.addListener(new BuildQueueListener()); } public File getConfigFile() { return configFile; } public void setVersionProperties(Properties versionProperties) { this.versionProperties = versionProperties; } public Properties getVersionProperties() { return versionProperties; } public void setConfigFile(final File configFile) throws CruiseControlException { if (configFile == null) { throw new CruiseControlException("No config file"); } if (!configFile.isFile()) { throw new CruiseControlException("Config file not found: " + configFile.getAbsolutePath()); } if (!configFile.equals(this.configFile)) { this.configFile = configFile; configManager = new XMLConfigManager(configFile, this); } loadConfig(); } private void addProject(final ProjectInterface project) throws CruiseControlException { project.configureProject(); projects.add(project); for (final Listener listener : listeners) { LOG.debug("Informing listener of added project " + project.getName()); listener.projectAdded(project); } project.setBuildQueue(buildQueue); project.start(); } private void removeProject(final ProjectInterface project) { projects.remove(project); for (final Listener listener : listeners) { LOG.debug("Informing listener of removed project " + project.getName()); listener.projectRemoved(project); } project.stop(); } public void resume() { buildQueue.start(); for (final ProjectInterface currentProject : projects) { currentProject.setBuildQueue(buildQueue); currentProject.start(); } } public void pause() { buildQueue.stop(); for (final ProjectInterface currentProject : projects) { currentProject.stop(); } } public void halt() { pause(); System.exit(0); } public String getBuildQueueStatus() { if (buildQueue.isAlive()) { if (buildQueue.isWaiting()) { return "waiting"; } else { return "alive"; } } else { return "dead"; } } public List<ProjectInterface> getProjects() { return Collections.unmodifiableList(projects); } private List<ProjectInterface> getAllProjects(XMLConfigManager configManager) { final Set<String> projectNames = configManager.getCruiseControlConfig().getProjectNames(); final List<ProjectInterface> allProjects = new ArrayList<ProjectInterface>(projectNames.size()); for (final String projectName : projectNames) { LOG.info("projectName = [" + projectName + "]"); final ProjectInterface projectConfig = getConfigManager().getProject(projectName); allProjects.add(projectConfig); } if (allProjects.size() == 0) { LOG.warn("no projects found in config file"); } return allProjects; } public XMLConfigManager getConfigManager() { return configManager; } public void addListener(final Listener listener) { LOG.debug("Listener added"); listeners.add(listener); } public void reloadConfigFile() { LOG.debug("reload config file called"); parseConfigFileIfNecessary(); } /** * @return true if the config file was parsed. */ public boolean parseConfigFileIfNecessary() { boolean reloaded = false; if (parsingConfigMutex.getPermissionToParse()) { try { try { reloaded = configManager.reloadIfNecessary(); } catch (CruiseControlException e) { LOG.error("error parsing config file " + configFile.getAbsolutePath(), e); return reloaded; } if (reloaded) { LOG.debug("config file changed"); loadConfig(); } else { LOG.debug("config file didn't change."); } } finally { parsingConfigMutex.doneParsing(); } } return reloaded; } private void loadConfig() { try { final List<ProjectInterface> projectsFromFile = getAllProjects(configManager); final List<ProjectInterface> removedProjects = new ArrayList<ProjectInterface>(projects); removedProjects.removeAll(projectsFromFile); final List<ProjectInterface> newProjects = new ArrayList<ProjectInterface>(projectsFromFile); newProjects.removeAll(projects); final List<ProjectInterface> retainedProjects = new ArrayList<ProjectInterface>(projects); retainedProjects.removeAll(removedProjects); //Handled removed projects for (final ProjectInterface removedProject : removedProjects) { removeProject(removedProject); } //Handle added projects for (final ProjectInterface newProject : newProjects) { addProject(newProject); } //Handle retained projects for (final ProjectInterface retainedProject : retainedProjects) { updateProject(retainedProject); } } catch (CruiseControlException e) { LOG.error("error parsing config file " + configFile.getAbsolutePath(), e); } } private void updateProject(ProjectInterface oldProject) throws CruiseControlException { ProjectInterface newProject = getConfigManager().getProject(oldProject.getName()); projects.remove(oldProject); newProject.getStateFromOldProject(oldProject); projects.add(newProject); } public static interface Listener extends EventListener { void projectAdded(ProjectInterface project); void projectRemoved(ProjectInterface project); } private class BuildQueueListener implements BuildQueue.Listener { public void buildRequested() { parseConfigFileIfNecessary(); } } public PluginDetail[] getAvailableBootstrappers() { return getPluginsByType(getAvailablePlugins(), PluginType.BOOTSTRAPPER); } public PluginDetail[] getAvailablePublishers() { return getPluginsByType(getAvailablePlugins(), PluginType.PUBLISHER); } public PluginDetail[] getAvailableSourceControls() { return getPluginsByType(getAvailablePlugins(), PluginType.SOURCE_CONTROL); } private static final PluginDetail[] EMPTY_PLUGIN_DETAIL = new PluginDetail[0]; public PluginDetail[] getAvailablePlugins() { try { return getPluginRegistry().getPluginDetails(); } catch (CruiseControlException e) { return EMPTY_PLUGIN_DETAIL; } } public PluginType[] getAvailablePluginTypes() { return getPluginRegistry().getPluginTypes(); } public PluginRegistry getPluginRegistry() { return configManager.getCruiseControlConfig().getRootPlugins(); } private static PluginDetail[] getPluginsByType(final PluginDetail[] details, final PluginType type) { final List<PluginDetail> plugins = new ArrayList<PluginDetail>(); for (final PluginDetail detail : details) { if (detail.getType().equals(type)) { plugins.add(detail); } } return plugins.toArray(new PluginDetail[plugins.size()]); } private class ParsingConfigMutex { private final Object mutex = new Object(); private boolean inUse; boolean getPermissionToParse() { synchronized (mutex) { if (inUse) { LOG.debug("permission denied to parse config"); return false; } inUse = true; LOG.debug("permission granted to parse config"); return true; } } void doneParsing() { LOG.debug("done parsing, allow next request permission to parse"); inUse = false; } } }