/******************************************************************************** * 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.jmx; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Properties; import javax.management.InstanceNotFoundException; import javax.management.InvalidAttributeValueException; import javax.management.JMException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import net.sourceforge.cruisecontrol.CruiseControlConfig; import net.sourceforge.cruisecontrol.CruiseControlController; import net.sourceforge.cruisecontrol.CruiseControlException; import net.sourceforge.cruisecontrol.PluginDetail; import net.sourceforge.cruisecontrol.PluginRegistry; import net.sourceforge.cruisecontrol.PluginType; import net.sourceforge.cruisecontrol.ProjectConfig; import net.sourceforge.cruisecontrol.ProjectInterface; import net.sourceforge.cruisecontrol.ProjectState; import net.sourceforge.cruisecontrol.gendoc.PluginInfo; import net.sourceforge.cruisecontrol.gendoc.PluginInfoParser; import net.sourceforge.cruisecontrol.gendoc.html.ConfigHtmlGenerator; import net.sourceforge.cruisecontrol.util.IO; import net.sourceforge.cruisecontrol.util.Util; import net.sourceforge.cruisecontrol.util.threadpool.ThreadQueue; import org.apache.log4j.Logger; import org.jdom.Element; /** * * @author <a href="mailto:robertdw@users.sourceforge.net">Robert Watkins</a> */ public class CruiseControlControllerJMXAdaptor extends NotificationBroadcasterSupport implements CruiseControlControllerJMXAdaptorMBean, CruiseControlController.Listener { private static final Logger LOG = Logger.getLogger(CruiseControlControllerJMXAdaptor.class); private static final Object SEQUENCE_LOCK = new Object(); private static int sequence = 0; private final CruiseControlController controller; private ObjectName registeredName; private MBeanServer server; public CruiseControlControllerJMXAdaptor(CruiseControlController controlController) { controller = controlController; controller.addListener(this); } public Properties getVersionProperties() { return controller.getVersionProperties(); } public String getConfigFileName() { return controller.getConfigFile() != null ? controller.getConfigFile().getAbsolutePath() : ""; } public String getConfigFileContents() { final File theConfigFile = controller.getConfigFile(); // guard clause if (theConfigFile == null) { return ""; } StringBuffer theResults = new StringBuffer(); try { final BufferedReader theConfigFileReader = new BufferedReader(new FileReader(theConfigFile)); try { // approximate the size theResults = new StringBuffer((int) theConfigFile.length()); readConfigFileContents(theResults, theConfigFileReader); } finally { theConfigFileReader.close(); } } catch (FileNotFoundException fne) { LOG.error("Configuration file not found", fne); //throw new CruiseControlException("Configuration file not found"); } catch (IOException ioe) { LOG.error("Error reading config file for JMX", ioe); //throw new CruiseControlException("Error reading config file"); } return theResults.toString(); } /** * Populate the given StringBuffer with the content of the config file * @param theResults will contain the config file contents * @param theConfigFileReader reader opened on the config file * @throws IOException if an error occurs */ static void readConfigFileContents(StringBuffer theResults, BufferedReader theConfigFileReader) throws IOException { String theCurrentLine = theConfigFileReader.readLine(); while (theCurrentLine != null) { theResults.append(theCurrentLine).append('\n'); theCurrentLine = theConfigFileReader.readLine(); } } public void setConfigFileContents(String contents) throws CruiseControlException { File theConfigFile = controller.getConfigFile(); // guard clause if config file not set if (theConfigFile == null) { return; } validateConfig(contents); try { // ensure the file exists Util.doMkDirs(theConfigFile); theConfigFile.createNewFile(); IO.write(theConfigFile, contents); } catch (FileNotFoundException fne) { LOG.error("Configuration file not found", fne); } catch (IOException ioe) { LOG.error("Error storing config file for JMX", ioe); } } public void validateConfig(String contents) throws CruiseControlException { InputStream in = new ByteArrayInputStream(contents.getBytes()); Element config = Util.loadRootElement(in); new CruiseControlConfig(config, controller); } public void setConfigFileName(String fileName) throws InvalidAttributeValueException { try { controller.setConfigFile(fileName != null ? new File(fileName) : null); } catch (CruiseControlException e) { throw new InvalidAttributeValueException(e.getMessage()); } } public List<ProjectInterface> getProjects() { return controller.getProjects(); } public List<String> getBusyTasks() { return ThreadQueue.getBusyTaskNames(); } public List<String> getIdleTasks() { return ThreadQueue.getIdleTaskNames(); } public PluginDetail[] getAvailableBootstrappers() { return controller.getAvailableBootstrappers(); } public PluginDetail[] getAvailablePublishers() { return controller.getAvailablePublishers(); } public PluginDetail[] getAvailableSourceControls() { return controller.getAvailableSourceControls(); } public PluginDetail[] getAvailablePlugins() { return controller.getAvailablePlugins(); } public PluginType[] getAvailablePluginTypes() { return controller.getAvailablePluginTypes(); } public PluginRegistry getPluginRegistry() { return controller.getPluginRegistry(); } public void resume() { controller.resume(); } public void pause() { controller.pause(); } public void reloadConfigFile() { controller.reloadConfigFile(); } public String getBuildQueueStatus() { return controller.getBuildQueueStatus(); } public void halt() { controller.halt(); } public void register(final MBeanServer server) throws JMException { this.server = server; this.registeredName = new ObjectName("CruiseControl Manager:id=unique"); server.registerMBean(this, this.registeredName); updateProjectMBeans(); } private void updateProjectMBeans() { LOG.debug("Updating project mbeans"); if (server != null) { for (final ProjectInterface project : controller.getProjects()) { projectAdded(project); } } } public void projectAdded(final ProjectInterface project) { try { project.register(server); } catch (JMException e) { LOG.error("Could not register project " + project.getName(), e); } final String name = "CruiseControl Project:name=" + project.getName(); LOG.debug("Adding project " + project.getName()); notifyChanged("projectAdded", name); } public void projectRemoved(final ProjectInterface project) { final String name = "CruiseControl Project:name=" + project.getName(); LOG.debug("Removing project " + name); try { final ObjectName projectName = new ObjectName(name); server.unregisterMBean(projectName); } catch (InstanceNotFoundException noProblem) { } catch (MBeanRegistrationException noProblem) { } catch (MalformedObjectNameException e) { LOG.error("Could not unregister project " + project.getName(), e); } notifyChanged("projectRemoved", name); } /** * Send a JMX notification that the list of projects has changed. * This only needs to be done when the project list changes, not * when a JMX server is registered. * <p/> * At the moment, we only absolutely know when a project is added or * removed, but not when the configuration file is reloaded or changed. * @param event event type * @param data event info */ private void notifyChanged(final String event, final String data) { final Notification notification = new Notification( "cruisecontrol." + event + ".event", this.registeredName, nextSequence()); notification.setUserData(data); sendNotification(notification); LOG.debug("Sent " + event + " event."); } private int nextSequence() { synchronized (SEQUENCE_LOCK) { return ++sequence; } } public Map<String, String> getAllProjectsStatus() { final Map<String, String> allStatus = new HashMap<String, String>(); for (final ProjectInterface projectInterface : getProjects()) { // @todo Is this cast always safe, and if so, do we need to clear up the typing of these? final ProjectConfig projectConfig = (ProjectConfig) projectInterface; final String projectName = projectConfig.getName(); String status = projectConfig.getStatus(); if (ProjectState.BUILDING.hasDescription(status)) { status = status + " since " + projectConfig.getBuildStartTime(); } else if (projectConfig.isPaused()) { status = ProjectState.PAUSED.getName(); } allStatus.put(projectName, status); } return allStatus; } /** * Gets a PluginInfo tree representing the plugins accepted by this server. * @param projectName Null to get the plugins from the root context. Otherwise, this should * be the name of a project on the server to use as a context for loading the plugin * tree. In that case, plugins defined by those projects will be included. * @return The PluginInfo tree. */ public PluginInfo getPluginInfo(final String projectName) { return parsePlugins(projectName).getRootPlugin(); } public List<PluginInfo> getAllPlugins(final String projectName) { return parsePlugins(projectName).getAllPlugins(); } /** * Generates the HTML documentation for the plugins. * @param projectName Null to get the plugins from the root context. Otherwise, this should * be the name of a project on the server to use as a context for loading the plugin * tree. In that case, plugins defined by those projects will be included in the * documentation. * @return The HTML content of the document. */ public String getPluginHTML(final String projectName) { final PluginInfoParser parser = parsePlugins(projectName); try { final ConfigHtmlGenerator gen = new ConfigHtmlGenerator(); return gen.generate(parser); } catch (Exception e) { e.printStackTrace(); return "ERROR; projectName: " + projectName + "; msg: " + e.getMessage(); } } public String getPluginCSS() { // Read the CSS from the file that is bundled with the JAR, in the gendoc.html // package. Return an empty string if there is any kind of error. // TODO: should load cruisecontrol.css and configxml-gendoc.css separately and concat // them together here, instead of depending on a build target to generate gendoc.css // for us. final InputStream stream = ConfigHtmlGenerator.class.getResourceAsStream("gendoc.css"); try { return IO.readText(stream); } catch (IOException e) { return ""; } } /** * Runs a parse of all available plugins, generating the PluginInfo tree. * @param projectName Null to get the plugins from the root context. Otherwise, this should * be the name of a project on the server to use as a context for loading the plugin * tree. In that case, plugins defined by those projects will be included. * @return The PluginInfoParser, after it has finished parsing. */ private PluginInfoParser parsePlugins(final String projectName) { // Get the PluginRegistry to use. final PluginRegistry registry; final CruiseControlConfig config = controller.getConfigManager().getCruiseControlConfig(); if (projectName == null) { // Use the root registry. registry = config.getRootPlugins(); } else { // Use a project-specific registry. registry = config.getProjectPlugins(projectName); if (registry == null) { throw new NoSuchElementException("Project " + projectName + " does not exist"); } } // Create a PluginInfoParser and invoke it to parse the PluginInfo tree. return new PluginInfoParser(registry, PluginRegistry.ROOT_PLUGIN); } }