Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to refactor snippet of old JSP to some JSF equivalent?

ORIGINAL JSP (WorkItem.jsp)

<c:forEach var="actionItem" items="${workItem.work_action_list}">
    <c:if test="${actionItem.workActionClass.work_action_type_id == '1'}" >
       <%@ include file="inc_done_button.jsp" %>
    </c:if>
    <c:if test="${actionItem.workActionClass.work_action_type_id == '2'}" >
         <c:set var="actionItem" value="${actionItem}" scope="request" />
         <c:set var="checklist" value="${actionItem.meat}" scope="request" />
        <jsp:include page="inc_dynamic_checklist_v.jsp" flush="true" />
    </c:if>
    etc...
</c:forEach>

ORIGINAL Java

for (ListIterator<WorkflowInstanceWorkItemAction> actionIter = wfiwi.getWork_action_list().listIterator(); actionIter.hasNext();) {
    if ("2".equals(work_action_type_id)) {
        ChecklistInstanceForm ciForm = new ChecklistInstanceForm(this, authenticatedUser);
         ChecklistInstance ci = null; 
        ci = (ChecklistInstance) ciForm.getChkLstInstanceByWfiWiaOwner(wfiWorkItemAction, authenticatedUser);
    // Get the meat details for this action and inject it into the object
        wfiWorkItemAction.setMeat(ci);
    }
}

request.setAttribute("workItem", wfiwi);
request.setAttribute("workFlowInstance", wfi); 

NEW JSF (WorkItem.xhtml)

 <f:metadata>
    <o:viewParam name="wfi_wid" value="#{workItemController.wfiwi}" converter="#{workItemConverter}"
    <f:event type="preRenderView" listener="#{workItemController.preRender}" />
 </f:metadata>
<ui:repeat var="actionItem" value="#{workItemController.wfiwi.work_action_list}">
    <ui:fragment rendered="#{actionItem.workActionClass.workActionType.action_type_id == '1'}">
        <stk:done_button actionItem="#{actionItem}" /> <!-- Here I chose custom c -->
    </ui:fragment>
    <ui:fragment rendered="#{actionItem.workActionClass.workActionType.action_type_id == '2'}">
                <ui:include src="inc_dynamic_checklist.xhtml">
                    <ui:param name="checklist" value="#{actionItem.meat}" />
                </ui:include>
    </ui:fragment>

The makings of my new backing bean

public class WorkItemController implements Serializable {
    private static final long serialVersionUID = 1L;
    private WorkflowInstanceWorkItem wfiwi;

    public void preRender() {
    if (wfiwi.getWork_action_list() != null) {
            //loop through and add real model to meat attribute

What I am after is a more elegant way to inject the model (what I am calling meat) into my view for each action. Under a work item (single page view), there are multiple actions. Actions that are checklists can be of various types (yes/no/na, quantity major/minor, yes/no/na/resolved, etc).

The composite component done_button was straight forward because I am only accessing the base action model and no meat. For example a snippet of the done_button.xhtml composite component

<ui:fragment rendered="#{cc.attrs.actionItem.is_active != '1'}">
     Action is not active for you until the following has been completed:
     <h:outputText value="#{cc.attrs.actionItem.prerequisite_work_action_list}" escapeXml="false" />
</ui:fragment>

but the include of the dynamic_checklist facelet code has me perplexed because my approach of injecting various Objects into this generic attribute meat :) seems wrong. In my original JSP I used <c:set var="checklist" value="${actionItem.meat}" scope="request" /> and then the original JSP for inc_dynamic_checklist_v.jsp looked something like

inc_dynamic_checklist_v.jsp

<form method="post" >

<c:out value="${actionItem.workActionClass.name}" /> 

<c:if test="${checklist.checkListClass.type == '1'}" >
  <%@ include file="inc_yes_no_na_resolved_checklist.jsp" %>
</c:if>

<c:if test="${checklist.checkListClass.type == '2'}" >
  <%@ include file="inc_major_minor_checklist.jsp" %>
</c:if>

<c:if test="${checklist.checkListClass.type == '3'}" >
  <%@ include file="inc_quantity_checklist.jsp" %>
</c:if>

<c:if test="${checklist.checkListClass.type == '4'}" >
  <%@ include file="inc_yes_no_na_checklist.jsp" %>
</c:if>

those includes also needed access to the actionItem.meat which was set using c:set in WorkItem.jsp

I'm looking for guidance as to yes I should convert all these includes into composite components, even though I have nested includes. Or I should use basic ui:includes? I know I can send param with either include or cc but do I still use the generic field private Object meat in my model or is there a better way to retrieve these individual action models.

perhaps this but it didn't work

<ui:include src="inc_dynamic_checklist.xhtml" >
    <ui:param name="wfi_id" value="#{actionItem.workflowInstance.workflow_instance_id}" />
    <ui:param name="wfi_aid" value="#{actionItem.wfi_work_item_action_id}" />
</ui:include>

and then in the inc_dynamic_checklist.xhtml

<f:metadata>
    <o:viewParam name="wfi_id" value="#{checklistInstanceView.ci}" converter="#{checklistInstanceConverter}">
        <f:attribute name="wfi_id" value="#{param.wfi_id}" />
        <f:attribute name="wfi_aid" value="#{param.wfi_aid}" />
    </o:viewParam>
</f:metadata>

UPDATE

Work item backing bean. A work Item contains an array of actions. Actions can be done buttons (action type id=1) checklists (action type id=2), and other things not implemented/shown. What I have now works but is it the right way?

public void preRender() {
if (wfiwi.getWork_action_list() != null) {

    for (ListIterator<WorkflowInstanceWorkItemAction> actionIter = wfiwi.getWork_action_list().listIterator(); actionIter.hasNext();) {

        WorkflowInstanceWorkItemAction wfiWorkItemAction = new WorkflowInstanceWorkItemAction();
        wfiWorkItemAction = actionIter.next();

        Long work_action_type_id = wfiWorkItemAction.getWorkActionClass().getWorkActionType().getAction_type_id();

        updatePrerequisites(wfiWorkItemAction, wfiwi.getWorkflowInstance(), wfiwi);

        if (work_action_type_id == 2) {
            System.out.println("Action Type 2 is Dynamic Checklist Type");
            ci = ciRepository.retrieveLatestByWfiWiai(wfiwi.getWorkflowInstance().getWorkflow_instance_id(), wfiWorkItemAction.getWfi_work_item_action_id());

            if (ci != null) {
                if ("1".equals(ci.getCheckListClass().getType())) {
                    List<YesNoNaResolvedAnswer> answer_attribute_list = yesNoNaResolvedDao.retrieveByCiWfi(ci.getChecklist_instance_id(), ci.getWorkflowInstance().getWorkflow_instance_id());
                    ci.setAnswer_attribute_list(answer_attribute_list);
                }

                if ("2".equals(ci.getCheckListClass().getType())) {
                    List<MajorMinorAnswer> answer_attribute_list = majorMinorAnsDao.retrieveByCiWfi(ci.getChecklist_instance_id(), ci.getWorkflowInstance().getWorkflow_instance_id());
                    ci.setAnswer_attribute_list(answer_attribute_list);
                }

                if ("3".equals(ci.getCheckListClass().getType())) {
                    List<QuantityAnswer> answer_attribute_list = quantityAnsDao.retrieveByCiWfi(ci.getChecklist_instance_id(), ci.getWorkflowInstance().getWorkflow_instance_id());
                    ci.setAnswer_attribute_list(answer_attribute_list);
                }
                if ("4".equals(ci.getCheckListClass().getType())) {
                    List<YesNoNaAnswer> answer_attribute_list = yesNoNaAnsDao.retrieveByCiWfi(ci.getChecklist_instance_id(), ci.getWorkflowInstance().getWorkflow_instance_id());
                    ci.setAnswer_attribute_list(answer_attribute_list);
                }

                wfiWorkItemAction.setMeat(ci);
            } else {
                Messages.addFlashErrorMessage("Could not find checklist Instance");
            }

            // wfi_action_list.add(ci);
        } else {
            wfiWorkItemAction.setMeat("meat pie");
        }
    }
}

}

inc_dynamic_checklist.xhtml (see WorkItem.xhtm above for how this is included) This is displaying the "meat"

    <ui:fragment rendered="#{checklist.checkListClass.type == '1'}">
        <ui:include src="inc_yes_no_na_resolved_checklist.xhtml" />
    </ui:fragment>

    <ui:fragment rendered="#{checklist.checkListClass.type == '2'}">
        <ui:include src="inc_major_minor_checklist.xhtml" />
    </ui:fragment>

    <ui:fragment rendered="${checklist.checkListClass.type == '3'}">
        <ui:include src="inc_quantity_checklist.xhtml" />
    </ui:fragment>

    <ui:fragment rendered="${checklist.checkListClass.type == '4'}">
        <ui:include src="inc_yes_no_na_checklist.xhtml" />
    </ui:fragment>

model

@Entity
public class WorkflowInstanceWorkItemAction implements Serializable {
private static final long serialVersionUID = 1L;
private String status;
private String is_active;

@Transient
private Object meat; 
and various mappings
like image 675
jeff Avatar asked Jul 30 '15 19:07

jeff


1 Answers

One step at a time.

It's important that everything keeps working as intented before advancing to next step.


Keep using JSTL to dynamically build the view

Just keep using JSTL and only replace JSP includes by <ui:include> until you get it all to work. Don't change too much yet. First get it all working and then refactor into tagfiles or composites.

In your initial JSP approach you're basically dynamically building the view with help of JSTL. You can just continue doing the same in JSF 2.x, provided that you're using a more recent JSF impl version to prevent broken view scoped beans (Mojarra 2.1.18+). You can keep using <c:forEach>, <c:if> and <c:set> this way in JSF. You only need to replace @include and <jsp:include> by <ui:include>. Do note that <ui:include> has the same lifecycle as JSTL. It's also a taghandler and not a component. See also JSTL in JSF2 Facelets... makes sense?

The <ui:fragment>, however, is an UI component. It does not conditionally build the view. Regardless of the outcome of its rendered attribute, it and all of its children will still end up in JSF component tree. They will only conditionally render their HTML output during render response phase. The payoff as compared to <c:if> is that the JSF component tree size will grow for every condition. It would grow at least 4 times as big, given that you've 4 conditional includes in that inc_dynamic_checklist_v file. Just keep using JSTL to dynamically build the view. It's a perfectly fine tool for that. See also a.o. How to make a grid of JSF composite component? The alternative would be to manually create components in backing bean via binding, findComponent(), createComponent(), new SomeComponent(), getChildren().add() and what not and this would only end up in verbose and brittle code which is hard to maintain. Absolutely don't do that.

The <f|o:viewParam> as shown in your failed attempt serves a different purpose. They can't act on <ui:param> values from the <ui:include>, as you seemed to expect. They act only on HTTP request parameters. See also What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for? You can for your <ui:include> keep using <ui:param> instead of <c:set>, but you should just be accessing them directly, like as you did with <c:set>. The only difference is that those variables are only available inside the include itself instead of in the entire request (i.e. thus also outside the include). The JSP equivalent of <ui:param> is by the way <jsp:param>, which you actually should have used in first place.

As to backing bean logic, just put the pre-processing Java code in @PostConstruct of the backing bean and the post-processing Java code in action methods of backing bean, tied to <h:commandXxx> components. The <f:viewAction> and preRenderView are insuitable because they run far after view build time and thus JSTL wouldn't get the model it expects. Use those only to process user-submitted HTTP request parameters.

If you're bitten by the chicken-egg view state bug in an older Mojarra version and you absolutely can't upgrade, nor can disable the partial state saving by setting javax.faces.PARTIAL_STATE_SAVING to false, then you can't attach JSTL tag attributes to view scoped bean properties. If you've indeed a view scoped bean here, and it's not an option to use a request scoped bean here, you'd need to drop JSTL and exclusively use <ui:repeat> and <ui:fragment> instead of <c:forEach> and <c:if>. You can however keep using <c:set> (where applicable). You should also keep the guidelines for backing bean logic as described above.


Refactor repeated include-with-params to tagfiles

Once you get it all to work, then you can start looking at repeated include-with-params (i.e. <ui:include><ui:param> chunks which are used more than once) and refactor them to tagfiles by simply registering them in your.taglib.xml file. This actually doesn't change anything as to the logic and flow, but makes the code more clean and concise. See also How to create a custom Facelets tag? for complete *.taglib.xml example and registration in web.xml.

This fictive example include of a "yes/no/na checklist"

<ui:include src="/WEB-INF/includes/tristateChecklist.xhtml">
    <ui:param name="value" value="#{actionItem}" />
</ui:include>

... could be used as below

<my:tristateChecklist value="#{actionItem}" />

... after moving the physical file into /WEB-INF/tags/tristateChecklist.xhtml and registering it in /WEB-INF/your.taglib.xml as below with all include params as tag attributes.

<tag>
    <tag-name>tristateChecklist</tag-name>
    <source>tags/tristateChecklist.xhtml</source>
    <attribute>
        <name>value</name>
        <type>java.lang.Object</type><!-- TODO: fix type -->
    </attribute>
</tag>

(you didn't show your model, so I just specified an overly generic type)


Refactor repeated model pre/post-processing to composites

Once you get it all to work again, then you can start looking at repeated model pre/post-processing and refactor them into composites with a "backing component", along with the associated XHTML inside <cc:implementation>.

Basically, when you have quite some Java code in @PostConstruct to convert the "external" model as returned by the service/DB to the "internal" model as exactly expected by the view, and/or when you have quite some Java code in action method to convert the "internal" model back to the "external" model as the service/DB expects, then you could consider refactoring it into a reusable composite component. This way you don't need to copypaste/repeat this pre/post-processing task into a different backing bean when you want to reuse the same functionality in a different view. And, you end up with a view which refers exactly the "external" model type instead of "internal" one, possibly consisting of multiple properies.

This part is hard to answer with an example for your specific case without having a full overview of all your model pre/post-processing. The below answers contain examples which should provide sufficient insight on the sense and nonsense of composite components:

  • Split java.util.Date over two h:inputText fields representing hour and minute with f:convertDateTime
  • Initialize a composite component based on the provided attributes
  • #{cc.clientId} evalutated in wrong composite after upgrading to JSF 2.2

At least, I have the impression that your "meat" could be an interface. If you have different objects/classes with same common behavior, then you should create an interface defining that common behavior and have those classes implement that interface. This part is in turn not strictly JSF related, but just "basic" Java.


Don't forget: one step at a time.

Use tagfiles and composites as a refactoring tool to minimize code duplication. You should already have fully working code.

like image 165
BalusC Avatar answered Nov 18 '22 14:11

BalusC