package hudson.drools;
import hudson.Extension;
import hudson.drools.renderer.RuleFlowRenderer;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildableItem;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.ResourceList;
import hudson.model.RunMap;
import hudson.model.TopLevelItem;
import hudson.model.TopLevelItemDescriptor;
import hudson.model.Cause.UserCause;
import hudson.model.Queue.Executable;
import hudson.model.RunMap.Constructor;
import hudson.scheduler.CronTabList;
import hudson.security.AuthorizationMatrixProperty;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.xpath.XPathExpressionException;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.DocumentException;
import org.drools.logger.KnowledgeRuntimeLoggerFactory;
import org.drools.runtime.process.ProcessInstance;
import org.drools.runtime.process.WorkItemManager;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import antlr.ANTLRException;
public class DroolsProject extends Job<DroolsProject, DroolsRun> implements
TopLevelItem, hudson.model.Queue.FlyweightTask, BuildableItem {
private boolean disabled;
private String processXML;
private transient DroolsSession session;
private String triggerSpec;
private transient CronTabList tabs;
private List<Script> scripts = new ArrayList<Script>();
private List<WorkItemAction> pendingBuilds = new ArrayList<WorkItemAction>();
/**
* All the builds keyed by their build number.
*/
protected transient/* almost final */RunMap<DroolsRun> builds = new RunMap<DroolsRun>();
protected DroolsProject(ItemGroup<?> parent, String name) {
super(parent, name);
}
@Override
protected SortedMap<Integer, ? extends DroolsRun> _getRuns() {
return builds.getView();
}
@Override
public boolean isBuildable() {
return true;
}
@Override
protected void removeRun(DroolsRun run) {
this.builds.remove(run);
}
@Override
public void onLoad(ItemGroup<? extends Item> parent, String name)
throws IOException {
super.onLoad(parent, name);
this.builds = new RunMap<DroolsRun>();
this.builds.load(this, new Constructor<DroolsRun>() {
public DroolsRun create(File dir) throws IOException {
DroolsRun newBuild = new DroolsRun(DroolsProject.this, dir);
builds.put(newBuild);
return newBuild;
}
});
try {
set(triggerSpec, processXML, scripts);
} catch (Exception e) {
disabled = true;
e.printStackTrace();
}
}
private int getMaxProcessInstanceId() {
int max = 0;
for (DroolsRun build: builds.values()) {
max = Math.max(max, (int) build.getProcessInstanceId());
}
return max;
}
void set(String triggerSpec, String processXML, List<Script> scripts)
throws IOException {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(
PluginImpl.class.getClassLoader());
try {
int initialId = getMaxProcessInstanceId() + 1;
DroolsSession session = processXML != null ? new DroolsSession(
new File(getRootDir(), "session.ser"), processXML, initialId) : null;
CronTabList tabs = null;
if (!StringUtils.isEmpty(triggerSpec)) {
try {
tabs = CronTabList.create(triggerSpec);
} catch (ANTLRException e) {
e.printStackTrace();
}
} else {
tabs = null;
triggerSpec = null;
}
// all is well -- let's commit
this.processXML = processXML;
this.session = session;
this.triggerSpec = triggerSpec;
this.tabs = tabs;
this.scripts = scripts != null ? scripts : new ArrayList<Script>();
WorkItemManager workItemManager = session.getSession()
.getWorkItemManager();
workItemManager.registerWorkItemHandler("Build",
new BuildWorkItemHandler(this));
workItemManager.registerWorkItemHandler("Human Task",
new HumanTaskHandler(this));
workItemManager.registerWorkItemHandler("Script",
new ScriptHandler(this));
workItemManager.registerWorkItemHandler("E-Mail",
new EmailWorkItemHandler());
KnowledgeRuntimeLoggerFactory
.newConsoleLogger(session.getSession());
new WorkingMemoryHudsonLogger(session.getSession(), this);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
@Extension
public static final class DescriptorImpl extends TopLevelItemDescriptor {
@Override
public String getDisplayName() {
return "Drools Project";
}
@Override
public DroolsProject newInstance(String name) {
return new DroolsProject(Hudson.getInstance(), name);
}
}
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) Hudson.getInstance().getDescriptor(
DroolsProject.class);
}
@Override
public Hudson getParent() {
return Hudson.getInstance();
}
@Override
public synchronized void doConfigSubmit(StaplerRequest req,
StaplerResponse rsp) throws IOException, ServletException {
checkPermission(CONFIGURE);
JSONObject form = req.getSubmittedForm();
String processXML = form.getString("processXML");
String triggerSpec = form.getString("triggerSpec");
List<Script> scripts = req.bindJSONToList(Script.class, form
.get("scripts"));
set(triggerSpec, processXML, scripts);
super.doConfigSubmit(req, rsp);
}
/**
* Schedules a new build command.
*/
public HttpResponse doBuild() throws IOException, ServletException {
checkPermission(BUILD);
Cause cause = new UserCause();
scheduleBuild(cause);
return new ForwardToPreviousPage();
}
public boolean scheduleBuild(Cause cause, Action... actions) {
if (isDisabled())
return false;
List<Action> queueActions = new ArrayList(Arrays.asList(actions));
if (cause != null) {
queueActions.add(new CauseAction(cause));
}
return Hudson.getInstance().getQueue().add(this, 0,
queueActions.toArray(new Action[queueActions.size()]));
}
public boolean isDisabled() {
return session == null || disabled;
}
public void setDisabled(boolean disable) {
this.disabled = disable;
}
public HttpResponse doEnable() {
checkPermission(CONFIGURE);
disabled = false;
return new ForwardToPreviousPage();
}
public void checkAbortPermission() {
checkPermission(AbstractProject.ABORT);
}
public Executable createExecutable() throws IOException {
DroolsRun run = new DroolsRun(this);
builds.put(run);
return run;
}
public Label getAssignedLabel() {
return null;
}
public long getEstimatedDuration() {
return -1;
}
public Node getLastBuiltOn() {
return null;
}
public String getWhyBlocked() {
return null;
}
public boolean hasAbortPermission() {
return false;
}
public boolean isBuildBlocked() {
return false;
}
public ResourceList getResourceList() {
return new ResourceList();
}
public String getProcessId() {
return session.getProcessId();
}
private transient WeakReference<RuleFlowRenderer> renderer;
public synchronized RuleFlowRenderer getRuleFlowRenderer() {
if (renderer == null || renderer.get() == null) {
renderer = new WeakReference<RuleFlowRenderer>(
new RuleFlowRenderer(processXML));
}
return renderer.get();
}
public void doProcessImage(StaplerRequest req, StaplerResponse rsp)
throws IOException, XPathExpressionException, DocumentException {
ServletOutputStream output = rsp.getOutputStream();
rsp.setContentType("image/png");
getRuleFlowRenderer().write(output);
output.flush();
output.close();
}
public void doProcessImageSVG(StaplerRequest req, StaplerResponse rsp)
throws IOException, XPathExpressionException, DocumentException {
ServletOutputStream output = rsp.getOutputStream();
rsp.setContentType("image/svg+xml");
getRuleFlowRenderer().writeSVG(output);
output.flush();
output.close();
}
@Exported
public String getProcessXML() {
return processXML;
}
@Exported
public String getTriggerSpec() {
return triggerSpec;
}
public boolean scheduleBuild() {
return scheduleBuild(null, new Action[0]);
}
public boolean scheduleBuild(Cause c) {
return scheduleBuild(c, new Action[0]);
}
public boolean scheduleBuild(int quietPeriod) {
return scheduleBuild(null, new Action[0]);
}
public boolean scheduleBuild(int quietPeriod, Cause c) {
return scheduleBuild(null, new CauseAction(c));
}
public CronTabList getTabs() {
return tabs;
}
public void doSubmitWorkflow(StaplerRequest request, StaplerResponse rsp)
throws IOException {
checkPermission(CONFIGURE);
if (!"POST".equals(request.getMethod())) {
rsp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"POST expected");
return;
}
String processXML = IOUtils.toString(request.getInputStream());
set(triggerSpec, processXML, scripts);
save();
}
@Override
protected void performDelete() throws IOException, InterruptedException {
if (session != null)
session.dispose();
super.performDelete();
}
public List<String> getUsersWithBuildPermission() {
List<String> result = new ArrayList<String>();
AuthorizationMatrixProperty amp = getProperty(AuthorizationMatrixProperty.class);
if (amp != null && amp.isUseProjectSecurity()) {
for (String sid : amp.getAllSIDs()) {
if (amp.hasPermission(sid, Job.BUILD)) {
result.add(sid);
}
}
}
return result;
}
/*
* We need two strategies two find the DroolsRun. When the process is
* starting, the DroolsRun does not know its processInstanceId yet, so we
* query the process variable "run".
*
* After the process is completed, the processInstance or variable will be
* gone, so we need to iterate over all the builds to find the right one.
*/
public DroolsRun getFromProcessInstance(long processInstanceId) {
DroolsRun result = null;
ProcessInstance processInstance = session.getSession()
.getProcessInstance(processInstanceId);
if (processInstance != null) {
result = DroolsRun.getFromProcessInstance(processInstance);
}
if (result == null) {
// probably because the workflow has been completed
for (DroolsRun run : getBuilds()) {
if (run.getProcessInstanceId() == processInstanceId) {
return run;
}
}
}
return result;
}
public <T> T run(SessionCallable<T> callable) throws Exception {
return session.run(callable);
}
public void dispose() {
session.dispose();
for (DroolsRun run : getBuilds()) {
run.dispose();
}
}
public List<Script> getScripts() {
return scripts;
}
public void setScripts(List<Script> scripts) {
this.scripts = new ArrayList<Script>(scripts);
}
public void setScripts(Script... scripts) {
this.scripts = new ArrayList<Script>(Arrays.asList(scripts));
}
public Script getScript(String scriptName) {
for (Script script : scripts) {
if (script.getId().equals(scriptName)) {
return script;
}
}
return DroolsManagement.getInstance().getScript(scriptName);
}
public DroolsSession getSession() {
return session;
}
public void addPendingWorkItemBuild(WorkItemAction action) throws IOException {
pendingBuilds.add(action);
save();
}
public void removePendingWorkItemBuild(WorkItemAction action) throws IOException {
pendingBuilds.remove(action);
save();
}
public List<WorkItemAction> getPendingBuilds() {
return pendingBuilds;
}
public HttpResponse doRescheduleWorkItemBuild(int workItemId) throws IOException {
for (WorkItemAction action: pendingBuilds) {
if (action.getWorkItemId() == workItemId) {
action.scheduleBuild();
return new ForwardToPreviousPage();
}
}
throw new IOException("Unknown work item id: " + workItemId);
}
public Object readResolve() {
if (pendingBuilds == null) pendingBuilds = new ArrayList<WorkItemAction>();
return this;
}
}