/*
* The MIT License
*
* Copyright (c) 2012, Cisco Systems, Inc., Max Spring
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.cisco.step.jenkins.plugins.jenkow;
import hudson.Util;
import hudson.util.FormValidation;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
class WfUtil {
private static final Logger LOGGER = Logger.getLogger(WfUtil.class.getName());
static FormValidation checkWorkflowName(String value) throws IOException, ServletException {
return checkFile(value,JenkowPlugin.getInstance().getRepo().getWorkflowFile(value));
}
private static FormValidation checkFile(String value, File f){
if (!StringUtils.isEmpty(value)){
if (!f.exists()){
String url = "descriptorByName/"+JenkowBuilder.class.getName()+"/createWorkflow?wfn="+value;
String msg = "Workflow "+value+" does not exist. "
+ "<button type=\"button\""
+ " onclick=\"javascript:"
+ "var e=this;"
+ "new Ajax.Request"
+ "("
+ "'"+url+"',"
+ "{onSuccess:function(x)"
+ "{notificationBar.show('Workflow created.',notificationBar.OK);"
+ "fireEvent($(e).up('TR.validation-error-area').previous().down('INPUT'),'change');"
+ "}"
+ "}"
+ ")"
+ "\""
+ ">"
+ "Create it now"
+ "</button>"
;
return FormValidation.errorWithMarkup(msg);
}
if (!f.canRead()) return FormValidation.warning("Workflow "+value+" is not readable.");
}
return FormValidation.ok();
}
static String launchWf(PrintStream log, String wfName, String parentName, Integer buildNo){
// test wfNames are full paths, doesn't work as workflow name
int p = wfName.lastIndexOf('/');
if (p > -1){
wfName = wfName.substring(p+1);
p = wfName.lastIndexOf('.');
if (p > -1) wfName = wfName.substring(0,p);
}
LOGGER.finer("WfUtil.launchWf: workflowName="+wfName);
ClassLoader previous = Thread.currentThread().getContextClassLoader();
RuntimeService rtSvc = JenkowEngine.getEngine().getRuntimeService();
try {
ProcessDefinition pDef = getDeployedWf(wfName);
if (pDef == null) throw new RuntimeException("no deployed process definition for "+wfName);
Map<String,Object> varMap = new HashMap<String,Object>();
// TODO 9: move jenkow variables into JenkowProcessData
varMap.put("jenkow_build_parent",parentName);
varMap.put("jenkow_build_number",buildNo);
varMap.put("console",new ConsoleLogger(parentName,buildNo));
JobMD.setJobs(varMap,JobMD.newJobs());
log.println(Consts.UI_PREFIX+": \""+wfName+"\" started");
long t = System.currentTimeMillis();
System.out.println("starting process "+pDef.getId());
// TODO 9: why is this blocking????
ProcessInstance proc = rtSvc.startProcessInstanceById(pDef.getId(),varMap);
// String procId = startProcess(pDef.getId(),varMap);
String procId = proc.getId();
System.out.println("process started procId="+procId+" ("+(System.currentTimeMillis()-t)+")");
return procId;
} finally {
Thread.currentThread().setContextClassLoader(previous);
}
}
static void deployAllToEngine(){
File repoDir = JenkowWorkflowRepository.getRepositoryDir();
if (!repoDir.exists()){
LOGGER.info("no workflow source repository");
return;
}
LOGGER.info("deploying all workflow engine");
RepositoryService repoSvc = JenkowEngine.getEngine().getRepositoryService();
Map<String,Date> deplTimes = new HashMap<String,Date>();
for (Deployment depl : repoSvc.createDeploymentQuery().list()){
//System.out.println(" depl: id="+depl.getId()+" name="+depl.getName()+" time="+depl.getDeploymentTime());
deplTimes.put(depl.getId(),depl.getDeploymentTime());
}
Map<String,Date> pDefTimes = new HashMap<String,Date>();
for (ProcessDefinition pDef : repoSvc.createProcessDefinitionQuery().latestVersion().list()){
//System.out.println(" pDef:"+pDef+" deplId="+pDef.getDeploymentId()+" key="+pDef.getKey());
Date t = deplTimes.get(pDef.getDeploymentId());
if (t != null) pDefTimes.put(pDef.getKey(),t);
}
for (Iterator it=FileUtils.iterateFiles(repoDir,new String[]{Consts.WORKFLOW_EXT},/*recursive=*/true); it.hasNext(); ){
File wff = (File)it.next();
String wfn = wff.getName();
int p = wfn.lastIndexOf('.');
if (p > -1) wfn = wfn.substring(0,p);
Date prevDeplTime = pDefTimes.get(wfn);
//System.out.println(" f="+wff+" wfn="+wfn+" deplTime="+prevDeplTime+" wff.lastModified="+new Date(wff.lastModified()));
if (prevDeplTime == null || prevDeplTime.before(new Date(wff.lastModified()))){
try {
WfUtil.deployToEngine(wff);
} catch (FileNotFoundException e) {
LOGGER.log(Level.SEVERE,"file not found "+wff,e);
}
}
}
}
static void deployToEngine(File wff) throws FileNotFoundException{
LOGGER.info("deploying "+wff+" to workflow engine");
ProcessEngine eng = JenkowEngine.getEngine();
RuntimeService rtSvc = eng.getRuntimeService();
RepositoryService repoSvc = eng.getRepositoryService();
String wfn = wff+"20.xml"; // TODO 9: workaround for http://forums.activiti.org/en/viewtopic.php?f=8&t=3745&start=10
DeploymentBuilder db = repoSvc.createDeployment().addInputStream(wfn,new FileInputStream(wff));
// TODO 4: We should avoid redeploying here, if workflow file of a given version(?) is already deployed?
Deployment d = db.deploy();
ProcessDefinition pDef = repoSvc.createProcessDefinitionQuery().deploymentId(d.getId()).singleResult();
LOGGER.fine("deployedToEngine("+wff+") --> "+pDef);
}
static ProcessDefinition getDeployedWf(String wfName){
RepositoryService repoSvc = JenkowEngine.getEngine().getRepositoryService();
if (LOGGER.isLoggable(Level.FINE)){
LOGGER.fine("deployed process definitions:");
for (ProcessDefinition pDef : repoSvc.createProcessDefinitionQuery().latestVersion().list()){
LOGGER.fine(" pDef:"+pDef+" id="+pDef.getId()+" key="+pDef.getKey()+" name="+pDef.getName()+" resourceName="+pDef.getResourceName()+" version="+pDef.getVersion());
}
}
ProcessDefinition pDef = repoSvc
.createProcessDefinitionQuery()
.processDefinitionKey(wfName)
.latestVersion()
.singleResult();
LOGGER.fine("getDeployedWf("+quoted(wfName)+") --> "+pDef);
return pDef;
}
static void generateWfDiagramTo(String wfName, OutputStream out) throws IOException{
ProcessDefinition pDef = getDeployedWf(wfName);
InputStream in = JenkowEngine.getEngine().getRepositoryService().getResourceAsStream(pDef.getDeploymentId(),pDef.getDiagramResourceName());
// TODO kk? should use copyStreamAndClose here?
Util.copyStream(in,out);
}
// experimental
// synchronized static String startProcess(final String pDefId, final Map<String,Object> vars) throws InterruptedException{
// ProcessEngine eng = JenkowEngine.getEngine();
// final RuntimeService rtSvc = eng.getRuntimeService();
//
// System.out.println("current ids:");
// Set<String> ids = new HashSet<String>();
// for (ProcessInstance proc : rtSvc.createProcessInstanceQuery().list()){
// String id = proc.getId();
// System.out.println(" rId="+id);
// ids.add(id);
// }
// for (HistoricProcessInstance proc : eng.getHistoryService().createHistoricProcessInstanceQuery().list()){
// String id = proc.getId();
// System.out.println(" hId="+id);
// ids.add(id);
// }
//
// System.out.println("starting process");
// new Thread(new Runnable(){
// @Override
// public void run() {
// rtSvc.startProcessInstanceById(pDefId,vars);
// }
// }).start();
// System.out.println("process started");
//
// while (true){
// for (ProcessInstance proc : rtSvc.createProcessInstanceQuery().list()){
// String id = proc.getId();
// System.out.println(" found running id="+id);
// if (!ids.contains(id)){
// return id;
// }
// }
// for (HistoricProcessInstance proc : eng.getHistoryService().createHistoricProcessInstanceQuery().list()){
// String id = proc.getId();
// System.out.println(" found historic id="+id);
// if (!ids.contains(id)){
// return id;
// }
// }
// Thread.sleep(100);
// }
// }
/**
* Produces a valid workflow ID out of a given string.
* Replaces each invalid character with an underscore.
*/
static String mkWorkflowId(String s){
if (s == null || s.length() < 1) return s;
// TODO 5: figure out what's the actual definition for valid workflow IDs
StringBuffer sb = new StringBuffer();
char c = s.charAt(0);
sb.append(Character.isJavaIdentifierStart(c)? c : '_');
for (int i=1,n=s.length(); i<n; i++){
c = s.charAt(i);
sb.append(Character.isJavaIdentifierPart(c)? c : '_');
}
return sb.toString();
}
static String quoted(String s){
return (s == null)? null : "\""+s+"\"";
}
}