1 2 3 Previous Next 30 Replies Latest reply on Oct 3, 2006 4:42 AM by frassi

    webapp development

      I'm currently getting more into building out stuff I'm working on with jbpm integrated, and also am hacking little things into the webapp that comes with the starter's kit. I'd like to get more info on the current state of that development and see what's going on.

      Unless I'm not reading my cvs output correctly (I've been using svn for the past couple years, so I may have some cross-mogenation with cvs), there haven't been any updates to the source tree at sourceforge for at least the past week under jbpm.3. Has the code been moved to wherever the rest of the jboss code sits at? Is there a new current branch for 3.1?

      I'd like to grab and use the latest for the webapp. I also might like to get involved with contributing. Specifically I've been working on something like JBPM-185, before I knew there was a JBPM-185, but just sort of my own hacked version of it. On a personal level, I've been building workflow based apps out of Bea's product for the past 3 years, which includes a large homegrown web front end. We've had a version of JBPM-185 in there since the start, so I think I might be able to contribute on that front. At the very least I'd like to be able to track and build current development.

      I picked up a jsf book specifically to try to understand what's going on in the webapp. I think I'm started to get it, although at first glance, jsf is A LOT different than my normal webapp thinking. I added a table with recently started process instances to the monitor front page, and the linking took a while to understand. I kind of get it now, but I still like the clarity of just "page.jsp?id=123" rather than the jsf version of things. Form pages are cool though.

      Ramble over.

      Thanks in advance,
      -Kevin

        • 1. Re: webapp development
          kukeltje

          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

            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

              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.java

              package 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

                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

                   

                  "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

                     

                    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

                       

                      <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

                        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

                           

                          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

                            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

                              great, i'll have a look

                              • 12. Re: webapp development

                                 


                                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

                                  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

                                    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.

                                    1 2 3 Previous Next