-
1. Re: webapp development
kukeltje Aug 7, 2005 5:08 PM (in response to kgalligan)There is indeed a 3.0 branch and a head. The 3.0 branche will result in 3.0.x releases whereas the head branch is for 3.x release (x>0)
It could be that not much is checked in lately due to other obligations (training etc.)
what is initially needed in the webapp is the ability to render variables in other forms then text input fields. So a boolean become two radiobuttons and/or a checkbox, Date will become a calender etc. We do think those should be in jsf faces.
So if you are into that, feel free to develop things. Keep us posted here or in the wiki and we'll see what we can use (and we are fairly liberal ;-) ) -
2. Re: webapp development
kgalligan Aug 7, 2005 8:30 PM (in response to kgalligan)Cool. I'll start digging into that.
Just to clarify, the main code is in the 'jbpm.3' folder. Head branch for 3.+ and 'branch_3_0' for current? I've gotten too used to subversion's physical branches. -
3. Re: webapp development
kgalligan Aug 11, 2005 12:11 AM (in response to kgalligan)Coming along with the form input issue. Can the variable type be defined in the process definition?
My code just uses the class type from the TaskFormParameter.getValue() object to decide what to do. So, if you're starting a process from the webapp, there's not much you can currently do. It will save the initial vars as text, and from there you're locked. If you start the process from your own code and pass the var objects in it works fine.
I was thinking maybe have a dropdown on the side of vars that are not yet defined, but I might not get around to that just yet.
If the value does exist, it does some special things. Boolean's show as a radio set. All others are text. Dates are formatted with the '<f:convertDateTime dateStyle="short" />' tag. On input, rudimentary validation is applied (Character is 1 char, Numbers convert properly and to their range, etc.). Validation problems return to the page and display with the <h:messages/> tag.
As for jsf, I think there's some good and bad. It does not play well with the back or refresh buttons. Also, the table model navigation is not good. See post...
http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=82&t=000383
The issue above is in the webapp. The process instances page off the process_definitions.jsp page for example. If an instance is submitted while you're looking at that page, all of your instances will link to the wrong detail page.
Looks like the same thing for tasks on the home page.
Any thought to maybe using standard navigation techniques and keeping jsf to the forms? I'm stepping way over my bounds with that statement considering I've done almost nothing so far, but I've been trying to figure out a good way to implement table lists in jsf and still haven't really come up with the answer. If you keep the data list in the session, you'll link correctly, but you'll have to manually tell your list to get new data from the database. Right now the data is pulled from the database on each page display, but has the potential for bad linking. Using 'page.jsp?id=123" removes these issues.
Assuming I haven't put off everbody with the above paragraph, here's stab #1 at my mods. Two files. task.jsp and TaskBean.java. I'd try to upload to the wiki, but they're not quite done. Cleanup, plus a problem with dates being submitted with bad formats (I'm missing something with jsf on this one).
PS. Any reason no lang commons from jakarta? I make a couple calls in there out of habit, but they can be pulled out.
task.jsp<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %> <%@ taglib uri="/WEB-INF/jbpm.tld" prefix="jbpm" %> <%@ page import="org.jbpm.webapp.bean.*" %> <%@ page import="org.jbpm.taskmgmt.exe.*" %> <f:view> <tiles:insert definition="page.template" flush="false"> <tiles:put name="title" type="string" value="Task"/> <tiles:put name="body" type="string"> <b><h:messages/></b> <table cellspacing="0" cellpadding="0" border="0"><tr><td valign="top"> <h:form id="taskform"> <h:inputHidden id="taskInstanceId" value="#{taskBean.taskInstanceId}" /> <h2><h:outputText value="#{taskBean.taskInstance.name}" /></h2> <hr /> <h:dataTable value="#{taskBean.taskFormParameters}" var="formParameter"> <h:column> <h:outputText value="#{formParameter.label}" /> </h:column> <h:column> <h:outputText value="#{formParameter.description}" /> </h:column> <h:column> <h:inputText value="#{formParameter.value}" readonly="#{formParameter.readOnly}" rendered="#{taskBean.other}"/> <h:inputText value="#{formParameter.value}" readonly="#{formParameter.readOnly}" rendered="#{taskBean.date}"> <f:convertDateTime dateStyle="short" /> </h:inputText> <h:selectOneRadio value="#{formParameter.value}" readonly="#{formParameter.readOnly}" rendered="#{taskBean.boolean}"> <f:selectItem itemValue="true" itemLabel="True"/> <f:selectItem itemValue="false" itemLabel="False"/> </h:selectOneRadio> </h:column> </h:dataTable> <hr /> <c:choose> <c:when test="${!empty taskBean.availableTransitions}"> Task Actions: <c:forEach var="availableTransition" items="${taskBean.availableTransitions}"> <c:set var="availableTransition" scope="request" value="${availableTransition}"/> <h:commandButton id="transitionButton" action="#{taskBean.saveAndClose}" value="#{availableTransition.name}"/> </c:forEach> </c:when> <c:otherwise> <h:commandButton id="transitionButton" action="#{taskBean.saveAndClose}" value="Save and Close Task"/> </c:otherwise> </c:choose> <hr /> <h:commandButton action="#{taskBean.save}" value="Save"/> <h:commandButton action="home" value="Cancel"/> </h:form> </td><td valign="top"> </td><td valign="top"> <jbpm:processimage task="${taskBean.taskInstanceId}"/> </td> </tr> </table> </tiles:put> </tiles:insert> </f:view>
TaskBean.javapackage org.jbpm.webapp.bean; import java.util.*; import javax.faces.model.SelectItem; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; import org.jbpm.db.GraphSession; import org.jbpm.db.JbpmSession; import org.jbpm.db.TaskMgmtSession; import org.jbpm.graph.def.Transition; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.logging.exe.LoggingInstance; import org.jbpm.taskmgmt.def.TaskController; import org.jbpm.taskmgmt.exe.TaskFormParameter; import org.jbpm.taskmgmt.exe.TaskInstance; import org.jbpm.taskmgmt.log.TaskAssignLog; import org.jbpm.webapp.context.Context; public class TaskBean { UserBean userBean = null; DataModel taskFormParameters; List availableTransitions; List availableTransitionItems; TaskInstance taskInstance; long taskInstanceId; GraphSession graphSession; TaskMgmtSession taskMgmtSession; // For monitoring purposes String name; String actorId; Date end; public TaskBean() { JbpmSession jbpmSession = Context.getPersistenceContext().getJbpmSession(); this.graphSession = jbpmSession.getGraphSession(); this.taskMgmtSession = jbpmSession.getTaskMgmtSession(); // get the parameters from the session this.taskFormParameters = (DataModel) JsfHelper.getSessionAttribute("taskFormParameters"); } /** * * Constructor uses by {@link org.jbpm.webapp.bean.ProcessInstanceBean} to monitor tasks list * * @param taskInstanceId The id of the task instance * @param name The task name * @param actorId The task actor Id * @param end The task end date * * @author David Loiseau * */ public TaskBean(long taskInstanceId,String name, String actorId,Date end) { this.taskInstanceId = taskInstanceId; this.name = name; this.actorId = actorId; this.end = end; } public void initialize(TaskInstance taskInstance) { this.taskInstance = taskInstance; this.taskInstanceId = taskInstance.getId(); // set the parameters this.taskFormParameters = new ListDataModel(); this.taskFormParameters.setWrappedData(taskInstance.getTaskFormParameters()); // store the parameters in the session JsfHelper.setSessionAttribute("taskFormParameters",taskFormParameters); // get the available transitions availableTransitions = null; availableTransitions = taskInstance.getAvailableTransitions(); if ( (availableTransitions!=null) && (availableTransitions.size()<=1) ) { availableTransitions = null; availableTransitionItems = null; } else { availableTransitionItems = new ArrayList(); Iterator iter = availableTransitions.iterator(); while (iter.hasNext()) { Transition transition = (Transition) iter.next(); SelectItem transitionItem = new SelectItem(); transitionItem.setValue(transition.getName()); transitionItem.setLabel(transition.getName()); transitionItem.setDisabled(false); availableTransitionItems.add(transitionItem); } } log.debug("initialized availableTransitions "+availableTransitions); } public String save() { log.debug("saving the task parameters "+taskFormParameters); TaskInstance taskInstance = taskMgmtSession.loadTaskInstance(taskInstanceId); // collect the parameter values from the values that were updated in the // parameters by jsf. Map submitParameters = new HashMap(); if(taskFormParameters != null) { List originalTaskFormParameters = taskInstance.getTaskFormParameters(); Map originalParamMap = new HashMap(); boolean paramError = false; for (int i = 0; i < originalTaskFormParameters.size(); i++) { TaskFormParameter taskFormParameter = (TaskFormParameter) originalTaskFormParameters.get(i); originalParamMap.put(taskFormParameter.getLabel(), taskFormParameter); } Iterator iter = ((List)taskFormParameters.getWrappedData()).iterator(); while (iter.hasNext()) { TaskFormParameter taskFormParameter = (TaskFormParameter) iter.next(); TaskFormParameter originalTaskFormParameter = (TaskFormParameter) originalParamMap.get(taskFormParameter.getLabel()); //Added check to original for null because if the user cleared the value, it wouldn't go away if ( (taskFormParameter.isWritable()) && (taskFormParameter.getValue()!=null || originalTaskFormParameter.getValue() != null) ) { String error = setParamValueType(originalTaskFormParameter, taskFormParameter); if(error != null) { JsfHelper.addMessage(error); paramError = true; continue; } log.debug("submitting ["+taskFormParameter.getLabel()+"]="+taskFormParameter.getValue()); submitParameters.put(taskFormParameter.getLabel(), taskFormParameter.getValue()); } else { log.debug("ignoring unwritable ["+taskFormParameter.getLabel()+"]"); } } if(paramError == true) return "error"; // submit the parameters in the jbpm task controller TaskController taskController = taskInstance.getTask().getTaskController(); log.debug("submitting task parameters: "+submitParameters); taskController.submitParameters(submitParameters, taskInstance); } // remove the parameters from the session JsfHelper.removeSessionAttribute("taskFormParameters"); graphSession.saveProcessInstance(taskInstance.getTaskMgmtInstance().getProcessInstance()); return "home"; } public String saveAndClose() { // save String retString = save(); if(StringUtils.isNotEmpty(retString)) return retString; // close the task instance String transitionButton = JsfHelper.getParameter("taskform:transitionButton"); log.debug("Submitted button:" + transitionButton); TaskInstance taskInstance = taskMgmtSession.loadTaskInstance(taskInstanceId); if ("Save and Close Task".equals(transitionButton)) { taskInstance.end(); } else { taskInstance.end(transitionButton); } ProcessInstance processInstance = taskInstance.getTaskMgmtInstance().getProcessInstance(); if (processInstance.hasEnded()) { JsfHelper.addMessage("The process has finished."); } LoggingInstance loggingInstance = processInstance.getLoggingInstance(); List assignmentLogs = loggingInstance.getLogs(TaskAssignLog.class); log.debug("assignmentlogs: "+assignmentLogs); if (assignmentLogs.size()==1) { TaskAssignLog taskAssignLog = (TaskAssignLog) assignmentLogs.get(0); JsfHelper.addMessage("A new task has been assigned to '"+taskAssignLog.getTaskNewActorId()+"'"); } else if (assignmentLogs.size()>1) { String msg = "New tasks have been assigned to: "; Iterator iter = assignmentLogs.iterator(); while (iter.hasNext()) { TaskAssignLog taskAssignLog = (TaskAssignLog) iter.next(); msg += taskAssignLog.getActorId(); if (iter.hasNext()) msg +=", "; } msg +="."; JsfHelper.addMessage(msg); } graphSession.saveProcessInstance(taskInstance.getTaskMgmtInstance().getProcessInstance()); return "home"; } public long getTaskInstanceId() { return taskInstanceId; } public void setTaskInstanceId(long taskInstanceId) { this.taskInstanceId = taskInstanceId; } public UserBean getUserBean() { return userBean; } public void setUserBean(UserBean userBean) { this.userBean = userBean; } public DataModel getTaskFormParameters() { return taskFormParameters; } public List getAvailableTransitions() { return availableTransitions; } public void setAvailableTransitions(List availableTransitions) { this.availableTransitions = availableTransitions; } public List getAvailableTransitionItems() { return availableTransitionItems; } public TaskInstance getTaskInstance() { return taskInstance; } private static final Log log = LogFactory.getLog(TaskBean.class); public String getActorId() { return actorId; } public void setActorId(String actorId) { this.actorId = actorId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getEnd() { return end; } public void setEnd(Date end) { this.end = end; } /*****************CODE FOR EXTRA FORM TYPES*****************/ public boolean isBoolean() { return (getCurrentVariableValue()!=null && getCurrentVariableValue() instanceof Boolean); } public boolean isDate() { return (getCurrentVariableValue()!=null && getCurrentVariableValue() instanceof Date); } public boolean isOther() { return (isBoolean() == false)&& (isDate() == false); } private Object getCurrentVariableValue() { TaskFormParameter tfParam = (TaskFormParameter)this.taskFormParameters.getRowData(); return tfParam.getValue(); } /** * Sets the value of taskFormParameter to the proper object type. Returns an error string if there's a problem. * * Date should be handled in * * @param originalTaskFormParameter * @param taskFormParameter * @return * @throws IllegalArgumentException */ private String setParamValueType(TaskFormParameter originalTaskFormParameter, TaskFormParameter taskFormParameter) { Object originalValue = originalTaskFormParameter.getValue(); if((taskFormParameter.getValue() instanceof String) == false) { //Assume the original object type is the same (right now has to be) return null; } //We'll make the assumption that all values will be strings right now String newValue = (String)taskFormParameter.getValue(); if(originalValue == null || newValue == null || originalValue instanceof String || newValue.length() == 0) return null; if(originalValue instanceof Character) { if(newValue.length() > 1) return "Form field '"+ originalTaskFormParameter.getLabel() +"' expects a Character"; taskFormParameter.setValue(CharUtils.toCharacterObject(newValue)); return null; } //This might require specific values, but we'll assume that this won't ever happen as the radio input //has been added if(originalValue instanceof Boolean) { taskFormParameter.setValue(new Boolean(newValue)); return null; } if(originalValue instanceof Number) { //This is the semi-dirty method. Not overly specific error message. try { if(originalValue instanceof java.lang.Float) taskFormParameter.setValue(new Float(newValue)); else if(originalValue instanceof java.lang.Double) taskFormParameter.setValue(new Double(newValue)); else if(originalValue instanceof java.lang.Integer) taskFormParameter.setValue(new Integer(newValue)); else if(originalValue instanceof java.lang.Long) taskFormParameter.setValue(new Long(newValue)); else if(originalValue instanceof java.lang.Byte) taskFormParameter.setValue(new Byte(newValue)); else if(originalValue instanceof java.lang.Short) taskFormParameter.setValue(new Short(newValue)); return null; } catch(NumberFormatException e) { return "Form field '"+ originalTaskFormParameter.getLabel() +"' expects a Number"; } } return "Original parameter type '"+ originalValue.getClass().getName() +"' not supported from form entry"; } }
-
4. Re: webapp development
kgalligan Aug 11, 2005 12:19 PM (in response to kgalligan)The discussion at javaranch has come up with a solution for the table linking which is to use the myfaces extension rather than the standard datatable implementation. This solves both issues, but would require a newer myfaces version...
http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=82&t=000383 -
5. Re: webapp development
kukeltje Aug 11, 2005 6:09 PM (in response to kgalligan)"kgalligan" wrote:
Coming along with the form input issue. Can the variable type be defined in the process definition?
Hmm..... I thought it was. Obviously it is not, at least not according to the docs. File a jira issue for it as an enhancement."kgalligan" wrote:
My code just uses the class type from the TaskFormParameter.getValue() object to decide what to do. So, if you're starting a process from the webapp, there's not much you can currently do. It will save the initial vars as text, and from there you're locked. If you start the process from your own code and pass the var objects in it works fine.
This will be solved as the variable type is added right? Again, file a Jira issue for it http://jira.jboss.org/jira/secure/BrowseProject.jspa?id=10052, btw, take this discussion to the Developer forum: http://www.jboss.org/index.html?module=bb&op=viewforum&f=219"kgalligan" wrote:
I was thinking maybe have a dropdown on the side of vars that are not yet defined, but I might not get around to that just yet.
In the editor? Otherwise I do not get this, but that is probably because of the late hour (past midnight here)
The rest has to wait till tomorrow.... yawn....(not because of boredom :-) ) -
6. Re: webapp development
kgalligan Aug 11, 2005 6:54 PM (in response to kgalligan)I was thinking maybe have a dropdown on the side of vars that are not yet defined, but I might not get around to that just yet.
In the task screen, to the left of the text box. A dropdown to select variable type. The more I think about it, though, that's pretty useless. That's something that might be useful during flow testing, but not when somebody is completing a task. Pretend I didn't even mention it.
As for setting default variables, I think that should open for more discussion in the forum before adding an enhancement request. For example, while less explicit, you can do this in an event as such...
startDate = new java.util.Date();
quantity = new Double(3.456);
At least I think you can. Going to try it out now. -
7. Re: webapp development
kgalligan Aug 11, 2005 6:55 PM (in response to kgalligan)<event type="process-start"> <script> <expression> startDate = new java.util.Date(); quantity = new Double(3.456); </expression> <variable name='startDate' access='write' /> <variable name='quantity' access='write' /> </script> </event>
-
8. Re: webapp development
kgalligan Aug 14, 2005 10:33 PM (in response to kgalligan)I put a zip file with changes for the type aware task params screen into the wiki...
http://wiki.jboss.org/wiki/Wiki.jsp?page=JbpmWebConsole
I have some other changes including a task reassignment page and "Recent Process Instances" link from the monitor page, but I'm not sure where I should be putting code. That, and the changes are kind of out of scope, so I left them out, but can post them if requested.
There's still a problem with the date upload. I used the myfaces extendsion 'inputDate' component. It works pretty well, but still bombs if you put garbage into the numeric fields. I had the same trouble with the standard text box and converter. If anybody has some advice with the date input, please let me know. -
9. Re: webapp development
kgalligan Sep 2, 2005 12:46 PM (in response to kgalligan)Keep us posted here or in the wiki and we'll see what we can use (and we are fairly liberal ;-) )
Ok. In keeping with the above, I've uploaded my current war build to the wiki. I don't really have any other way of posting stuff, and there's changes to the libs, so I thought that would be easiest. Here's my changes:
- I added the latest nightly myfaces build. It looks like they will release the .10 version very soon, so that should be swapped in when that comes out. The reason I added this was to use the extDataTable and specifically 'forceIdIndexFormula', which links tables by id value instead of index. See my ramblings:
http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=82&t=000422
I've changed a number of the tables to use that. In short, the reason is that if something is updated after you get your page but before you click the link, you'll get the wrong record. An example is the task screen. If you go to home, you'll see your tasks. If a task is added to your list before you click the link, you'll be "clicking" on a different task because the table is "link-by-index" rather than "link-by-id".
- Changed to client state storage. This is not required. The only reason I did this is so the back button works generally as expected.
- "Optimized" the query in the MonitorBean. It originally was a query per process definition. I changed the query so that its just 1 query with a group by. Was this avoided originally for portability?
- Added "Recent Process Instances" to the monitoring section. This will list the proess instances started in the past week, with the newest at top. I found this to be very useful during testing. Start a process, then open this page and see it, rather than hunting for it.
- MyBean. I added this when I started. I thought I took it out. Sorry.
- Reassign tasks. I added a page to the admin section to allow task reassignment. This is a very, very simple page. A from and to box, which just take strings. These should be usernames. There is NO validation because of the open nature of the identity managment. Type a name in the from and to box. Click "Reassign All" to move all tasks from "from" user to "to" user. Click "List Specific Tasks" to see all tasks currently assigned to the "from" user. Select specific tasks and click "Reassign Specific Tasks" to move them, or you can still click "Reassign All" (I'd suggest clicking "List Specific Tasks" first even if you plan to reassign all just to check that you have the right user).
- Added the common-lang jar. I just like it. Its use is limited (MonitoringBean, ReassignBean, TaskBean), so it could be pretty easily removed.
My code is steadily diverging from what's in cvs, so I figured I should get this out there and see if you want to retain anything. -
10. Re: webapp development
alkero Sep 5, 2005 5:06 AM (in response to kgalligan)Hello Kevin,
Good news if my faces .10 version includes this ameliorations, it?s really better than in the actual .9 version.
About the query in the MonitorBean, can you copy/paste your query here?
The recent process instances list is a good idea. Fell free to send me your code, I will add this to the cvs version.
Regards,
David -
11. Re: webapp development
kukeltje Sep 5, 2005 8:28 AM (in response to kgalligan)great, i'll have a look
-
12. Re: webapp development
kgalligan Sep 5, 2005 10:45 AM (in response to kgalligan)
About the query in the MonitorBean, can you copy/paste your query here?
The query is...String sqlRequest = "select count(processdefinition_) as instCount, processdefinition_ "+ "from jbpm_processinstance " + "where processdefinition_ in (" + StringUtils.join(inArray, ',') + ") group by processdefinition_";
Its in a different spot in the code. Original...public List getProcessDefinitions() { ArrayList processDefinitionsList = new ArrayList(); JbpmSession jbpmSession = Context.getPersistenceContext().getJbpmSession(); List processDefinitions = jbpmSession.getGraphSession().findAllProcessDefinitions(); if (processDefinitions.isEmpty() == false) { ListIterator listProcessDefinitions = processDefinitions.listIterator(); while (listProcessDefinitions.hasNext() ) { ProcessDefinition processDefinition = (ProcessDefinition)listProcessDefinitions.next(); int instancesCount = 0; try { Connection connection = jbpmSession.getConnection(); Statement statement = connection.createStatement(); String request = "SELECT COUNT(*) AS instancesCount " + "FROM jbpm_processinstance " + "WHERE processdefinition_='" + processDefinition.getId() + "'"; ResultSet resultSet = statement.executeQuery(request); resultSet.next(); instancesCount = resultSet.getInt("instancesCount"); } catch (Exception e) {} processDefinitionsList.add( new ProcessDefinitionBean( processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), instancesCount )); } } return(processDefinitionsList); }
I basically split the loop, and changed the query to only go to the database once. I'm sure there's a better way to code the loop than I did, but I wanted to change the logic as little as possible...public List getProcessDefinitions() { ArrayList processDefinitionsList = new ArrayList(); JbpmSession jbpmSession = Context.getPersistenceContext().getJbpmSession(); List processDefinitions = jbpmSession.getGraphSession().findAllProcessDefinitions(); if (processDefinitions.isEmpty() == false) { Map processDefinitionCountMap = new HashMap(); //Do a little optimization String[] inArray = new String[processDefinitions.size()]; int inArrayCount = 0; ListIterator listProcessDefinitions = processDefinitions.listIterator(); while (listProcessDefinitions.hasNext() ) { ProcessDefinition processDefinition = (ProcessDefinition)listProcessDefinitions.next(); inArray[inArrayCount++] = Long.toString(processDefinition.getId()); } String sqlRequest = "select count(processdefinition_) as instCount, processdefinition_ "+ "from jbpm_processinstance " + "where processdefinition_ in (" + StringUtils.join(inArray, ',') + ") group by processdefinition_"; try { Connection connection = jbpmSession.getConnection(); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sqlRequest); while(resultSet.next()) { processDefinitionCountMap.put(new Long(resultSet.getLong("processdefinition_")), new Integer(resultSet.getInt("instCount"))); } } catch(Exception e){e.printStackTrace();} listProcessDefinitions = processDefinitions.listIterator(); while (listProcessDefinitions.hasNext() ) { ProcessDefinition processDefinition = (ProcessDefinition)listProcessDefinitions.next(); int intInstancesCount = -1; Integer instancesCount = (Integer) processDefinitionCountMap.get(new Long(processDefinition.getId())); if(instancesCount != null) intInstancesCount = instancesCount.intValue(); processDefinitionsList.add( new ProcessDefinitionBean( processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), intInstancesCount )); } } return(processDefinitionsList); }
The recent process instances list is a good idea. Fell free to send me your code, I will add this to the cvs version.
Jsp page 'recent_process_instances.jsp'...<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %> <f:view locale="#{facesContext.externalContext.request.locale}"> <f:loadBundle basename="messages" var="msgs"/> <tiles:insert definition="page.template" flush="false"> <tiles:put name="title" type="string" value="Recent Process Instances"/> <tiles:put name="body" type="string"> <b><h:messages/></b> <br /> <%--This should be converted to use apache's extended table and reference the specific id to avoid--%> <%--trouble with database updates in the backgrond (many tables in the app should do the same). --%> <%--As of today, that extended table is still in the nightly builds only, so until 1.0.10, or 1.1 of--%> <%--myfaces comes out, stick with this. THIS SHOULD NOT BE RUN IN A HEAVY PRODUCTION SYSTEM--%> <x:dataTable value="#{monitoringBean.recentProcessInstances}" var="processInstance" forceIdIndexFormula="#{processInstance.id}" headerClass="tableheader" columnClasses="tablecell" > <h:column> <f:facet name="header"> <h:outputText value="#{msgs.id}"/> </f:facet> <h:commandLink action="#{monitoringBean.inspectRecentProcessInstance}" > <h:outputText value="#{processInstance.id}"/> </h:commandLink> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{msgs.name}"/> </f:facet> <h:outputText value="#{processInstance.processDefinition.name}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{msgs.start}"/> </f:facet> <h:outputText value="#{processInstance.start}"> <f:convertDateTime type="both"/> </h:outputText> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{msgs.end}"/> </f:facet> <h:outputText value="#{processInstance.end}"> <f:convertDateTime type="both"/> </h:outputText> </h:column> </x:dataTable> </tiles:put> </tiles:insert> </f:view>
Added to MonitorBean.java...DataModel recentProcessInstances; public DataModel getRecentProcessInstances() { Calendar dateCompare = new GregorianCalendar(); dateCompare.add(Calendar.DAY_OF_WEEK, -7); JbpmSession jbpmSession = Context.getPersistenceContext().getJbpmSession(); List retList = jbpmSession.getSession().createQuery("from ProcessInstance pi where pi.start >= :startDate") .setDate("startDate", dateCompare.getTime()) .list(); Collections.sort(retList, new Comparator(){ public int compare(Object a, Object b) { ProcessInstance p1 = (ProcessInstance) a; ProcessInstance p2 = (ProcessInstance) b; return p2.getStart().compareTo(p1.getStart()); } }); recentProcessInstances = new ListDataModel(); recentProcessInstances.setWrappedData(retList); return recentProcessInstances; } public String inspectRecentProcessInstance() { ProcessInstance procInst = (ProcessInstance)recentProcessInstances.getRowData(); ProcessInstanceBean processInstanceBean = new ProcessInstanceBean(procInst.getId()); FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("processInstanceBean", processInstanceBean); return("inspectInstance"); }
I've also changed the ProcessImageTag to be able to track multiple tokens in a single process image. It uses absolute divs instead of the table structure. The output looks like...
[img]http://www.kgalligan.com/docroot/kevin/mutitoken.gif[/img]public class ProcessImageTag extends TagSupport { private static final long serialVersionUID = 1L; private long taskInstanceId = -1; private long tokenInstanceId = -1; private boolean absolutePosition = false; private byte[] gpdBytes = null; private byte[] imageBytes = null; private Token currentToken = null; private ProcessDefinition processDefinition = null; public void release() { taskInstanceId = -1; gpdBytes = null; imageBytes = null; currentToken = null; } public int doEndTag() throws JspException { try { initialize(); retrieveByteArrays(); if (gpdBytes != null && imageBytes != null) { writeTable(); } } catch (IOException e) { e.printStackTrace(); throw new JspException("table couldn't be displayed", e); } catch (DocumentException e) { e.printStackTrace(); throw new JspException("table couldn't be displayed", e); } release(); return EVAL_PAGE; } private void retrieveByteArrays() { try { FileDefinition fileDefinition = processDefinition.getFileDefinition(); gpdBytes = fileDefinition.getBytes("gpd.xml"); imageBytes = fileDefinition.getBytes("processimage.jpg"); } catch (Exception e) { e.printStackTrace(); } } private void writeTable() throws IOException, DocumentException { int borderWidth = 4; Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes)).getRootElement(); int[] imageDimension = extractImageDimension(rootDiagramElement); String imageLink = "processimage?definitionId=" + processDefinition.getId(); JspWriter jspOut = pageContext.getOut(); if (isAbsolutePosition()) { jspOut.println("<div style='position:relative; background-image:url(" + imageLink + "); width: " + imageDimension[0] + "px; height: " + imageDimension[1] + "px;'>"); List allTokens = new ArrayList(); walkTokens(currentToken, allTokens); for (int i = 0; i < allTokens.size(); i++) { Token token = (Token) allTokens.get(i); int[] boxConstraint = extractBoxConstraint(rootDiagramElement, token); //Adjust for borders boxConstraint[2]-=borderWidth*2; boxConstraint[3]-=borderWidth*2; jspOut.println("<div style='position:absolute; left: "+ boxConstraint[0] +"px; top: "+ boxConstraint[1] +"px; "+ "border: red " + borderWidth + "px groove; "+ "width: "+ boxConstraint[2] +"px; height: "+ boxConstraint[3] +"px;'>"); if(StringUtils.isNotEmpty(token.getName())) { jspOut.println("<span style='color:blue;font-style:italic;position:absolute;left:"+ (boxConstraint[2] + 5) +"px;'>" + StringEscapeUtils.escapeHtml(token.getName()) +"</span>"); } jspOut.println("</div>"); } jspOut.println("</div>"); } else { int[] boxConstraint = extractBoxConstraint(rootDiagramElement); jspOut.println("<table border=0 cellspacing=0 cellpadding=0 width=" + imageDimension[0] + " height=" + imageDimension[1] + ">"); jspOut.println(" <tr>"); jspOut.println(" <td width=" + imageDimension[0] + " height=" + imageDimension[1] + " style=\"background-image:url(" + imageLink + ")\" valign=top>"); jspOut.println(" <table border=0 cellspacing=0 cellpadding=0>"); jspOut.println(" <tr>"); jspOut.println(" <td width=" + (boxConstraint[0] - borderWidth) + " height=" + (boxConstraint[1] - borderWidth) + " style=\"background-color:transparent;\"></td>"); jspOut.println(" </tr>"); jspOut.println(" <tr>"); jspOut.println(" <td style=\"background-color:transparent;\"></td>"); jspOut.println(" <td style=\"border-color:red; border-width:" + borderWidth + "px; border-style:groove; background-color:transparent;\" width=" + boxConstraint[2] + " height=" + (boxConstraint[3] + (2 * borderWidth)) + "> </td>"); jspOut.println(" </tr>"); jspOut.println(" </table>"); jspOut.println(" </td>"); jspOut.println(" </tr>"); jspOut.println("</table>"); } } private int[] extractBoxConstraint(Element root) { return extractBoxConstraint(root, currentToken); } private int[] extractBoxConstraint(Element root, Token token) { int[] result = new int[4]; String nodeName = token.getNode().getName(); XPath xPath = new DefaultXPath("//node[@name='" + nodeName + "']"); Element node = (Element) xPath.selectSingleNode(root); result[0] = Integer.valueOf(node.attribute("x").getValue()).intValue(); result[1] = Integer.valueOf(node.attribute("y").getValue()).intValue(); result[2] = Integer.valueOf(node.attribute("width").getValue()).intValue(); result[3] = Integer.valueOf(node.attribute("height").getValue()).intValue(); return result; } private int[] extractImageDimension(Element root) { int[] result = new int[2]; result[0] = Integer.valueOf(root.attribute("width").getValue()).intValue(); result[1] = Integer.valueOf(root.attribute("height").getValue()).intValue(); return result; } private void initialize() { JbpmSession jbpmSession = JbpmSession.getCurrentJbpmSession(); if (this.taskInstanceId > 0) { TaskInstance taskInstance = jbpmSession.getTaskMgmtSession().loadTaskInstance(taskInstanceId); currentToken = taskInstance.getToken(); } else { if (this.tokenInstanceId > 0) currentToken = jbpmSession.getGraphSession().loadToken(this.tokenInstanceId); } processDefinition = currentToken.getProcessInstance().getProcessDefinition(); } public void setTask(long id) { this.taskInstanceId = id; } public void setToken(long id) { this.tokenInstanceId = id; } private void walkTokens(Token parent, List allTokens) { Map children = parent.getChildren(); if(children != null && children.size() > 0) { Collection childTokens = children.values(); for (Iterator iterator = childTokens.iterator(); iterator.hasNext();) { Token child = (Token) iterator.next(); walkTokens(child, allTokens); } } allTokens.add(parent); } public boolean isAbsolutePosition() { return absolutePosition; } public void setAbsolutePosition(boolean absolutePosition) { this.absolutePosition = absolutePosition; } }
I uploaded a version of my code to...
http://wiki.jboss.org/wiki/Wiki.jsp?page=JbpmWebConsole
That doesn't have the ProcesImageTag changes, but I can upload another copy with them if anybody's interested.[/url] -
13. Re: webapp development
davidsan1001 Oct 22, 2005 9:18 PM (in response to kgalligan)I'm loving this stuff. I recently ran into this issue as I'm learning about jBPM. I'm hoping to test this out soon and use it to create some radio buttons, pop-out menus and whatever else it will allow me to create.
I noticed the last posting was a while back, did this discussion move somewhere?
Dave -
14. Re: webapp development
davidsan1001 Oct 23, 2005 4:34 PM (in response to kgalligan)Still trying to get my head around your work earlier in this posting where you are adding support for object other than strings in the taskFormParameterValues.
I'm working on a way to support pop-out menus and thought we could extend your changes on TaskBean.java (at about line 350) to support java.lang.List. It seems JSF support creating a pop-out menu from a List. Here is some code for that...
<h:selectOneMenu id="selectCar"
value="#{carBean.currentCar}">
<f:selectItems
value="#{carBean.carList}" />
</h:selectOneMenu>
So, using the above, in an Action Class you could create a variable called "currentCar" of type string and a variable "carList" of type List. So the users selection gets saved as "currentCar"
Any thoughts? As you can tell I'm not the most experienced Java, J2EE or jBPM programmer but am very motivated so help build pop-out menu capabitlies.