通过WebService控制CruiseControl进行构建

CruiseControl(CC)作为持续集成的工具,为了方便管理,在其源码中就提供了许多JMX的接口,其中有主程序本身的,有Dashboard的,还有我们今天想要提到的Project的JMX接口,这使得我们可以很方便的通过CC的RMI端口去调用JMX对Project进行构建

这其中涉及到的接口和类主要有以下几个:

ProjectMBean接口,我们今天调用的方法是build()方法

public interface ProjectMBean {

    /**
     * Pauses the controlled project.
     */
    public void pause();

    /**
     * Resumes the controlled project.
     */
    public void resume();

    /**
     * Runs a build now
     */
    public void build();

    /**
     * Runs a build now, overriding the target of the used builder
     * @param target the target to invoke
     */
    public void buildWithTarget(String target);

    /**
     * Runs a build now, overriding the target of the used builder
     * and passing additional properties
     * @param target the target to invoke
     * @param addedProperties the additional properties that will be passed to the build
     */
    public void buildWithTarget(String target, Map<String, String> addedProperties);

    /**
     * Serialize the project
     */
    public void serialize();

    /**
     * Is the project paused?
     *
     * @return Pause state
     */
    public boolean isPaused();

    /**
     *
     * @return start time of the last build, using the format 'yyyyMMddHHmmss'
     */
    public String getBuildStartTime();

    /**
     * Change the Project label
     *
     * @param label a new label; should be valid for the current
     * LabelIncrementer
     */
    public void setLabel(String label);

    public String getLabel();

    /**
     * Change the last built date.  This can be used to manipulate whether
     * builds will be initiated.
     *
     * @param date date string in the form yyyyMMddHHmmss
     * @throws CruiseControlException if an invalid date string is given
     */
    public void setLastBuild(String date) throws CruiseControlException;

    public String getLastBuild();

    public boolean isLastBuildSuccessful();

    /**
     * Change the last built date.  This can be used to manipulate whether
     * builds will be initiated.
     *
     * @param date date string in the form yyyyMMddHHmmss
     * @throws CruiseControlException if an invalid date string is given
     */
    public void setLastSuccessfulBuild(String date) throws CruiseControlException;

    public String getLastSuccessfulBuild();

    /**
     * Change the directory where CruiseControl logs are kept
     *
     * @param logdir Relative or absolute path to the log directory
     */
    public void setLogDir(String logdir);

    public String getLogDir();

    /**
     * @return a list with the names of the available log files
     */
    public List<String> getLogLabels();

    /**
     * @param logLabel a valid build label, must exist in the list returned by {@link #getLogLabels()}.
     * @param firstLine the starting line in the log for the given build label
     * @return lines from the given firstLine up to max lines, or an empty array if no more lines exist.
     */
    public String[] getLogLabelLines(String logLabel, int firstLine);

    /**
     * Change the project name.  May cause problems if configuration file is
     * not also changed.
     * @param name the new project name
     */
    public void setProjectName(String name);

    public String getProjectName();

    /**
     * Change the interval between builds
     *
     * @param buildInterval Build interval in milliseconds
     */
    public void setBuildInterval(long buildInterval);

    public long getBuildInterval();

    /**
     * Gets the human-readable version of the project status.
     * @return the human-readable version of the project status
     */
    public String getStatus();

    /**
     * @return the commit message includes the commiter and message
     */
    public String[][] commitMessages();


    /**
     * @param firstLine The starting line to skip to.
     * @return Output from the live output buffer, after line specified (inclusive).
     */
    public String[] getBuildOutput(Integer firstLine);

    /**
     * @return  A unique (for this VM) identifying string for this logger instance.
     * 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();
}
ProjectMBean

以及实现ProjectMBean接口的ProjectController类,build()方法会结束Project的持续构建等待直接开始构建

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();
    }
}
ProjectController

我们可以注意到在ProjectController注册到代理时,使用的ObjectName是CruiseControl Project:name=ProjectName

    public static final String OBJECT_NAME_PREFIX = "CruiseControl Project:name=";
    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);
    }

 

让我们来尝试通过RMI调用CC的JMX接口构建示例Project:connectfour,新建一个Test类

environment中存放的是RMI服务端的访问地址、RMI的JNDI工厂类,由于CC运行在本地因此为localhost,RMI端口则是CC配置中默认的1099

public class Test {
    public static void main(String[] args) {
        try {
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost:1099/jndi/jrmp");
            Map<String, String> environment = new HashMap<String, String>();
            environment.put("java.naming.provider.url", "rmi://localhost:1099");
            environment.put("java.naming.factory.initial", "com.sun.jndi.rmi.registry.RegistryContextFactory");    
            JMXConnector jmxc = JMXConnectorFactory.connect(url, environment);
            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
            ObjectName mbeanName = new ObjectName("CruiseControl Project:name=connectfour");
            mbsc.invoke(mbeanName, "build", null, null);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            System.out.println(e.getMessage());
        }
    }
}

 

先把CC启动起来,确认Project:connectfour的存在,然后运行Test类中的main函数,可以在CC的命令行窗口看到connectfour开始进行构建

到这里我们已经成功的通过RMI调用CC的JMX接口进行Project的构建了,但是如果我们希望非Java语言的系统也能进行这样的调用的话,就得请出WebService了

 

首先新建一个CruiseControlAdmin类,实现构建Project的方法BuildProject()

public class CruiseControlAdmin {

    public void BuildProject(String projectName) {
        try {
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost:1099/jndi/jrmp");
            Map<String, String> environment = new HashMap<String, String>();
            environment.put("java.naming.provider.url", "rmi://localhost:1099");
            environment.put("java.naming.factory.initial", "com.sun.jndi.rmi.registry.RegistryContextFactory");    
            JMXConnector jmxc = JMXConnectorFactory.connect(url, environment);
            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
            ObjectName mbeanName = new ObjectName("CruiseControl Project:name=" + projectName);
            mbsc.invoke(mbeanName, "build", null, null);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            System.out.println(e.getMessage());
        }
    }
    
}

然后为了能够被识别为WebService,我们需要services.xml

<service name="CruiseControlAdminService">  
    <description>CruiseControlAdminService</description>  
    <messageReceivers>
        <messageReceiver mep="http://www.w3.org/ns/wsdl/in-only" class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" />
        <messageReceiver  mep="http://www.w3.org/ns/wsdl/in-out"  class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </messageReceivers>
    <parameter name="ServiceClass" locked="false">com.reno.cruisecontrol.adminservice.main.CruiseControlAdmin</parameter> 
</service>  

将编译出的CruiseControlAdmin.class和services.xml打成一个aar包,比如CruiseControlAdmin.aar

将这个aar包放在如Apache Axis2的WEB-INF\services下,或者直接通过Apache Axis2的admin管理页面upload这个aar包,这样就算部署WebService成功了

最后就是验证一下WebService是否可用了,写文章的时间不早了,就不再进行验证了

 

到这里,有了WebService的帮助,我们就可以在多平台多语言下控制CruiseControl进行Project的构建了

 

posted @ 2017-01-04 19:18  Ddddddday  阅读(157)  评论(0编辑  收藏  举报