Skip to main content
Version: QB13

Plugin Data Storage

Plugin might need to have a place on server to store build related data. For example, JUnit plugin stores processed JUnit test data for each build into the build storage area after JUnit publish step runs, and use that data to render JUnit report upon UI access. Normally this place is a directory choosed by plugins under the Build Publish Directory, and files can be stored into the place by calling publish method of the build object.

Again we demonstrate this by enhancing myplugin to store the user specified message at server when the step runs so that it can be rendered into build overview screen.

First modify MyAnotherStep.java as below to save message into a file and got it published to server.

package com.example.myplugin;

import java.io.File;

import org.hibernate.validator.NotEmpty;

import com.pmease.quickbuild.annotation.Editable;
import com.pmease.quickbuild.annotation.Scriptable;
import com.pmease.quickbuild.stepsupport.Step;
import com.pmease.quickbuild.util.FileUtils;
import com.pmease.quickbuild.Context;

@Editable(category="examples", name="publish message", description=
"This step publishes an user defined message and render them on build overview screen.")
public class MyAnotherStep extends Step {

private static final long serialVersionUID = 1L;

private String message;

@Editable
@NotEmpty
@Scriptable
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

@Override
public void run() {
File tempDir = FileUtils.createTempDir();
FileUtils.writeFile(new File(tempDir, "message.txt"), getMessage());
try {
// call publish to transfer files to server node. Do not simply
// copy files here since the step might be running on an agent
// node.
Context.getBuild().publish(tempDir.getAbsolutePath(), null, "myplugin");
} finally {
FileUtils.deleteDir(tempDir);
}
}

}

And then modify MyPanel.java like below to render the message in published file.

package com.example.myplugin;

import java.io.File;

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;

import com.pmease.quickbuild.Context;
import com.pmease.quickbuild.util.FileUtils;

public class MyPanel extends Panel {

private static final long serialVersionUID = 1L;

public MyPanel(String id) {
super(id);

String message = FileUtils.readFileAsString(
new File(Context.getBuild().getPublishDir(), "myplugin/message.txt"));
add(new Label("message", message));
}

}

Finally modify MyPlugin.java to contribute the message panel only when sub directory myplugin exists.

package com.example.myplugin;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.markup.html.panel.Panel;

import com.pmease.quickbuild.extensionpoint.BuildOverviewContribution;
import com.pmease.quickbuild.extensionpoint.StepProvider;
import com.pmease.quickbuild.extensionpoint.support.PanelCreator;
import com.pmease.quickbuild.pluginsupport.AbstractPlugin;
import com.pmease.quickbuild.stepsupport.Step;
import com.pmease.quickbuild.Context;

public class MyPlugin extends AbstractPlugin {

@Override
public Object[] getExtensions() {
return new Object[] {
new StepProvider() {

@Override
public Class<? extends Step> getStepClass() {
return MyStep.class;
}

},
new StepProvider() {

@Override
public Class<? extends Step> getStepClass() {
return MyAnotherStep.class;
}

},
new BuildOverviewContribution() {

public List<PanelCreator> getPanelCreators() {
List<PanelCreator> creators = new ArrayList<PanelCreator>();

if (new File(Context.getBuild().getPublishDir(), "myplugin").exists()) {
creators.add(new PanelCreator() {

public Panel getPanel(String id) {
return new MyPanel(id);
}

});
}
return creators;
}

public int getOrder() {
return 500;
}
}
};
}

}

Now start QuickBuild, and run the root configuration. You will see the published message file under directory <global storage directory>/builds/<build id>/myplugin , where <global storage directory> is the directory you specified when set up QuickBuild server, and <build id> refers to id of the newly generated build and is displayed at the build overview screen.

Overview screen of the newly generated build will also contains a panel displaying your specified message when define the step.

Besides storing build related data, plugin may also need to store configuation related data to server. Still taking the JUnit plugin for example, it accumulates test statistics information for a configuration overtime and use it to render JUnit statistics chart upon UI access. The Configuration Publish Directory is a good place to store such data, and plugins can create sub directories under this place to hold their own data if necessary.

We demonstrate this by enhancing myplugin to introduce a new statistics tab displaying a line chart to draw the trend of number of chars in the message across different builds.

First define a new class MyStatistics.java to hold statistics data as below:

package com.example.myplugin;

import java.util.LinkedHashMap;
import java.util.Map;

public class MyStatistics {

// map build version to message length
private Map<String, Integer> msgLens = new LinkedHashMap<String, Integer>();

public Map<String, Integer> getMsgLens() {
return msgLens;
}

public void setMsgLens(Map<String, Integer> msgLens) {
this.msgLens = msgLens;
}

}

And add a new class MyMetricsCollector.java as below to collect message char count for current build and update the statistics data:

package com.example.myplugin;

import java.io.File;

import com.pmease.quickbuild.extensionpoint.StatisticsSupport;
import com.pmease.quickbuild.model.Build;
import com.pmease.quickbuild.util.BeanUtils;
import com.pmease.quickbuild.util.FileUtils;
import com.pmease.quickbuild.Context;

public class MyMetricsCollector implements StatisticsSupport {

public String getStatisticsName() {
return "My Statistics";
}

public void collectStatistics(Build build) {
/*
* This method will be called from server after build finishes. So we can
* access the message file and statistics file directly.
*/
File msgFile = new File(build.getPublishDir(), "myplugin/message.txt");

// collect metrics only if message publishing step was executed for this build.
if (msgFile.exists()) {
int msgLen = FileUtils.readFileAsString(msgFile).length();
File statsFile = new File(build.getConfiguration().getPublishDir(), "myplugin/statistics.xml");
MyStatistics stats;
if (statsFile.exists())
stats = (MyStatistics) BeanUtils.readFile(statsFile);
else
stats = new MyStatistics();
stats.getMsgLens().put(build.getVersion(), msgLen);
BeanUtils.writeFile(stats, statsFile);
}
}

/* This method will be called before rebuilding the statistics upon request */
public void cleanupStatistics(Configuration configuration) {
File statsFile = new File(build.getConfiguration().getPublishDir(), "myplugin/statistics.xml");
if (statsFile.exists())
statsFile.delete();
}

}

Then add MyStatisticsPanel.java and MyStatisticsPanel.html to draw the chart using statistics data.

  • MyStatisticsPanel.java

    package com.example.myplugin;

    import java.io.File;
    import java.util.Map;

    import org.apache.wicket.markup.html.panel.Panel;
    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.JFreeChart;
    import org.jfree.chart.plot.PlotOrientation;
    import org.jfree.data.category.DefaultCategoryDataset;

    import com.pmease.quickbuild.Context;
    import com.pmease.quickbuild.util.BeanUtils;
    import com.pmease.quickbuild.web.chart.JFreeChartPanel;

    public class MyStatisticsPanel extends Panel {

    private static final long serialVersionUID = 1L;

    public MyStatisticsPanel(String id) {
    super(id);

    File statsFile = new File(Context.getConfiguration().getPublishDir(),
    "myplugin/statistics.xml");
    MyStatistics stats = (MyStatistics) BeanUtils.readFile(statsFile);
    DefaultCategoryDataset dataset = new DefaultCategoryDataset();
    for (Map.Entry<String, Integer> entry: stats.getMsgLens().entrySet())
    dataset.addValue(entry.getValue(), "", entry.getKey());

    JFreeChart chart = ChartFactory.createLineChart("Message Char Count Statistics",
    "build", "count", dataset, PlotOrientation.VERTICAL,
    false, false, false);
    add(new JFreeChartPanel("chart", 800, 400, chart));
    }

    }
  • MyStatisticsPanel.html

    <wicket:panel>
    <div wicket:id="chart"></div>
    </wicket:panel>

Finally modify MyPlugin.java to implement extension point StatisticsSupport and StatisticsTabContribution:

package com.example.myplugin;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.wicket.markup.html.panel.Panel;

import com.pmease.quickbuild.extensionpoint.BuildOverviewContribution;
import com.pmease.quickbuild.extensionpoint.StatisticsTabContribution;
import com.pmease.quickbuild.extensionpoint.StepProvider;
import com.pmease.quickbuild.extensionpoint.support.PanelCreator;
import com.pmease.quickbuild.pluginsupport.AbstractPlugin;
import com.pmease.quickbuild.stepsupport.Step;
import com.pmease.quickbuild.web.component.tabbedpanel.Tab;
import com.pmease.quickbuild.Context;

public class MyPlugin extends AbstractPlugin {

@Override
public Object[] getExtensions() {
return new Object[] {
new StepProvider() {

@Override
public Class<? extends Step> getStepClass() {
return MyStep.class;
}

},
new StepProvider() {

@Override
public Class<? extends Step> getStepClass() {
return MyAnotherStep.class;
}

},
new BuildOverviewContribution() {

public List<PanelCreator> getPanelCreators() {
List<PanelCreator> creators = new ArrayList<PanelCreator>();
if (new File(Context.getBuild().getPublishDir(), "myplugin").exists()) {
creators.add(new PanelProvider() {

public Panel getPanel(String id) {
return new MyPanel(id);
}

});
}
return creators;
}

public int getOrder() {
return 500;
}
},
new MyMetricsCollector(),
new StatisticsTabContribution() {

public List<Tab> getTabs() {
List<Tab> tabs = new ArrayList<Tab>();
if (new File(Context.getConfiguration().getPublishDir(), "myplugin").exists()) {
tabs.add(new Tab("message char count") {

private static final long serialVersionUID = 1L;

@Override
public Panel getPanel(String id, Map<String, String> params) {
return new MyStatisticsPanel(id);
}

});
}
return tabs;
}

public int getOrder() {
return 500;
}
}
};
}

}

Save modifications, start QuickBuild, and run the root configuation several times (remember to change the message length a little bit before each run to make the line chart varies). Switch to statistics tab of the root configuration, and you will see the "message char count" tab displaying statistics chart of number of message chars.