I am using Spring 3.x and WebFlow 2.0. I am trying to submit the form with a new event id.
<view-state id="projectSearch" view="project.projectSearch" model="searchCommand">
<transition on="search" to="projectSearch" bind="true" >
<evaluate expression="formAction.findProjects(flowRequestContext, searchCommand)"/>
</transition>
</view-state>
and
public Event findProjects(RequestContext context, SearchCommand command) throws Exception
{
return success();
}
On form submit I set the event key to search. With above configuration I do not receive any request to findProject() method. But if I remove model="" attribute from view-state I get the request received by the method. In this case I do not get form values.
Can anybody please direct me the correct path.
Thanks,
Vishal
It appeared to be jQuery for submit was causing the problem. If I comment the jQuery submit it works. I changed few lines and removed javascript need for form submit.
Related
I have a use case where I need to redirect to an external URL via spring webflow, the url might change every time the user clicks on the link. I am trying to use externalRedirect within the flow.xml and passing in the url in a localAttributeMap and returning it with the event.
The summary.xhtml code looks something like this.
<h:commandLink action="redirect" target="_blank">
Redirect to new page
</h:commandLink>
The flow.xml is something like this:
<view-state id="summary" view="/flows/forms/summary.xhtml">
<transition on="redirect" to="retrieveUri" />
</view-state>
<action-state id="retrieveUri">
<evaluate expression="redirectAction.execute(requestContext, formContext)" />
<transition on="successRedirect" to="externalView" />
</action-state>
<view-state id="externalView" view="externalRedirect:#{currentEvent.attributes.redirectUrl}">
</view-state>
On the code base I am using I am forced to only use Actions which extend MultiAction and I can't use any other service have return types like String. My action class with the execute method which returns an Event looks something like below:
public Event execute(RequestContext context, FormContext formContext) {
LocalAttributeMap lam = new LocalAttributeMap() ;
lam.put("redirectUrl", "https://google.com") ;
//Above would of course be replaced with some service layer call but I need to redirect to this url and that too via classes extending MultiAction.
return new Event(this, " successRedirect", lam) ;
}
However this does not seem to work for me and I am getting the below errors:
Caused by: org.springframework.binding.expression.EvaluationExpression : An ElException occured getting the value for expression 'currentEvent.attributes.redirectUrl' on context [class org.springftamework.webflow.engine.impl.RequestControlContextImpl]
at org.springframework.binding.expression.spel.SpringElExpression.getValue(SpringElExpression.java:92)
at org.springframework.webflow.action.ExternalRedirectAction.doExecute(ExternalRedirectAction.java:42)
Caused by: org.springframework.expression.spel.SpelEvaluationExpression : EL1007E:(pos 13): Property or field 'attributes' cannot be found on null
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:208)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:85)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:78)
Being new to spring webflow, I am not sure of the issue and would appreciate some help in understanding the issue and whether is the correct way of using this.
The version of spring-webflow is 2.3.0.RELEASE within the project
Background
A user must submit report parameters to the server. The server redirects the user to a URL. That URL runs Oracle Reports to produce a PDF (or web page).
The system uses a relatively slow authentication mechanism. But I'm not looking to restart the web flow, so the authentication should not otherwise interfere with the redirect.
JSP
The "Run Report" button is coded as follows:
<button type="submit" id="run" name="_eventId_run">
<fmt:message key="form.button.report.run" />
</button>
The rest of the page binds the parameters to a map in the DAO, such as:
<form:radiobutton path="parameters['service']" class="service" value="..." />
Submitting the form shows that the bind variables are set in the DAO map correctly. Further, the report is able to generate the URL used for redirection.
The form itself resembles:
<form:form modelAttribute="report" action="${flowExecutionUrl}"
method="post">
<fieldset>
<%-- secret tokens --%>
<tiles:insertAttribute name="hidden" />
<%-- includes the requested report form parameters --%>
<jsp:include page="${reportKey}.jsp" />
<%-- includes the aforementioned submit button %-->
<tiles:insertAttribute name="reportButtons" />
</fieldset>
</form:form>
Flow
The flow has three view states: list reports, enter parameters, and run report. The last two are pertinent:
<view-state id="parameters" model="report" view="flow/reports/parameters">
<transition on="run" to="redirect">
<evaluate expression="reportService.run(report)" result="flowScope.url" />
</transition>
</view-state>
<view-state id="redirect" view="externalRedirect:#{flowScope.url}"/>
The reportService.run(report) method is being called. The report parameters are being bound. The return result is, indeed, the correct URL.
Java
The report service itself is reasonably trivial:
public String run(Report report) {
Map<String, String> parameters = report.getParameters();
String url = getConfigurationValue("ReportUrl");
for (String key : parameters.keySet()) {
info("Report Parameter: {} = {}", key, parameters.get(key));
}
return url;
}
Again, the correct url is being returned. There is no controller class. The Report DAO could hardly be simpler:
public class Report extends DAOBase implements Serializable {
/** Values to pass into the report. */
private Map<String, String> parameters;
public Report() {
setParameters(createParameters());
}
// standard accessors and serial version not shown
}
Problem
It appears that the application is performing a POST-Redirect-GET (PRG). The GET does not direct the browser to the URL set by flowScope.url. (I have not verified that flowScope.url contains valid data.) When the POST operation completes, the application is redirected to the parameter flow, rather than the run button redirecting to the redirect flow.
From the schema definition, everything seems correct.
Questions
Using Spring 4.1.2, what needs to change so that the externalRedirect sends the browser to the URL returned by reportService.run(...)?
Does the form need to supply a different value for flowExecutionUrl?
Attempts
Here are some of the various changes that have been made to no avail:
Use $ in the EL, instead of # (i.e., externalRedirect:${flowScope.url})
Use <end-state id="redirect" view="externalRedirect:#{flowScope.url}"/>
Use <view-state id="redirect" view="externalRedirect:http://google.com"/>
Use form:button instead of button
Changing the redirect view-state and removing the expression evaluation causes the redirect to fire. For example:
<transition on="run" to="redirect">
<!-- <evaluate expression="reportService.run(report)" result="flowScope.url" /> -->
</transition>
...
<view-state id="redirect" view="externalRedirect:http://google.com"/>
This, of course, means that the report parameters are never used, which won't work.
Remove the evaluation from the transition:
<transition on="run" to="redirect"/>
Call the run method within an EL statement to generate the URL:
<view-state id="redirect" view="externalRedirect:#{reportService.run(report)}"/>
I am using Spring Web Flow 2.3 and I have a page that has two forms on it that transition to different places depending on which is submitted. To accomplish this, I have one composite model object for my view-state that holds the two forms inside. The problem I am seeing is that if transition A is fired, I only want to validate form A, and likewise with form B - only want to validate B if B transition fired. I am not sure how to indicate which form to validate. View state that is validating the entire compositeForm for each transition:
<view-state model="compositeForm">
<transition on="formAsubmit" to="formApage" validate="true"/>
<transition on="formBsubmit" to="formBpage" validate="true"/>
</view-state>
Does anyone know how I can trigger a custom validator to validate differently depending on which transition was fired?
Thanks for you help.
Steve
I don't know about a custom validator for each, but within your validation method, I think you could use the RequestContextHolder.getRequestContext() to getCurrentTransition() or getCurrentEvent() and compare manually to the getId() value.
What I ended up doing was to manually trigger my validation when form B was submitted and transition to a decision-state that checks if there were validation errors. It's a little ugly, but I feel like it's the best way:
<view-state id="start" model="compositeForm">
<transition on="formAsubmit" to="pageA" validate="true"/>
<transition on="formBsubmit" to="isFormBValid" validate="false">
<evaluate expression="formBValidator.validate(compositeForm.formB, messageContext)"/>
</transition
</view-state>
<decision-state id="isFormBValid">
<if test="messageContext.hasErrorMessages()" then="start" else="pageB"/>
</decision-state>
This is not the best solution, but at least is solves the problem. This is how I obtained my transition id and view-state id in vlaidator.
Transition id
RequestContextHolder.getRequestContext().getFlowExecutionContext().getActiveSession().getState().getId();
view-state id
RequestContextHolder.getRequestContext().getFlowExecutionContext().getActiveSession().getState().getId();
Bit of a background:
We have an existing spring webflow that we want to have ajaxified so that the page can be displayed
in a "lightbox" (at a different URL) where the user can interact with the flow in a similar way to the full existing page.
The normal registration form sits at
http://localhost:8080/csso/customer/registration?execution=e1s1.
(csso is the application name)
The webflow in Spring Webflow has an id of /customer/registration.
<flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<flow-location id="customer/registration" path="/WEB-INF/views/customer/registration/registration-flow.xml"/>
</flow-registry>
registration-flow.xml
<view-state id="create" model="customer" view="customer/registration/create">
<on-render>
<evaluate expression="customer.setAcceptTermsAndConditions(false)"/>
</on-render>
<transition on="submit" to="confirm" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
Now my problem is I need to access this page in two different ways.
Directly. Which works currently.
As an include in a different JSP page
(tabEntry.jsp accessed with controller /tabEntry) in such a way that the flow of the form is not interrupted.
How can I include /customer/registration inside tabEntry in such a way that clicking on submit goes through the same flow?
Problems faced:
Including /customer/registration by jsp:include doesn't work since /customer/registration is not a controller. Also #include doesn't work since a call to /customer/redirection includes a redirect which sets the execution key for webflow
The form action is automatically set to /tabEntry but it should be set to the second state of /customer/registation to continue with the flow.
Happy to accept design changes instead of hammering it technically.
I'm doing a project in seam that requires restful URLs. I have a view that is mapped to /group/{group}/{locale}. On that page I have a list of so called messages. Each message has a button to save changes to the message. This is linked to an action bean like this:
<h:commandButton type="submit" value="Save Changes" action="#{groupAction.update}" />
Each message has an anchor, so /group/{group}/{locale}#{id} can be used to make the browser go to that anchor. This is why I need a redirect after the POST:
<page view-id="/group.xhtml">
<rewrite pattern="/group/{group}/{locale}"/>
<param name="group" value="#{groupAction.group}"/>
<param name="locale" value="#{groupAction.locale}"/>
<navigation from-action="#{groupAction.update}">
<redirect view-id="/group.xhtml?group=#{group}&locale=#{locale}##{id}"/>
</navigation>
</page>
Also I have the following redirect rule (UrlRewriteFilter) to get to the proper RESTful URL:
<outbound-rule>
<from>^/group.xhtml\?group=([\.\w]+)&locale=([\.\w]+)\#([\.\w]+)\?cid=(\d*)$</from>
<to type="temporary-redirect" last="true">/group/$1/$2#$3</to>
</outbound-rule>
I strip the conversationId here. This has been tested an works. However seam still appends a '?conversationId={cid}'. So what's the problem? Well, imagine a URL like '/group/{group}/{locale}#{id}?conversationId={cid}'. Oviously the browser doesn't like this and will not automatically go to that anchor.
I did some research and discovered my problem in the seam documentation:
29.1.4.2. Conversation propagation with redirects
This filter allows Seam to propagate the conversation context across browser redirects. It intercepts any browser redirects and adds a request parameter that specifies the Seam conversation identifier. The redirect filter will process all requests by default, but this behavior can also be adjusted in components.xml:
<web:redirect-filter url-pattern="*.seam"/>
I don't need the redirect-filter and I tried putting something invalid in the url-pattern to "disable" the filter. However that didn't work. So my question is now:
How do I disable the redirect-filter in seam?
I can't find the answer. The seam documentation talks about disabling it in web.xml, but my attempts have not been succesful yet.
I've worked out where the unwanted conversationId query string parameter is coming from.
The <redirect/> results in a call to org.jboss.seam.faces.FacesManager.redirect(String viewId, Map<String, Object> parameters,
boolean includeConversationId)
This is called from the following code in org.jboss.seam.faces.Navigator which sets includeConversationId to true:
FacesManager.instance().redirect(viewId, parameters, true);
I cannot see any way to avoid this, so the fix/workaround is to do the redirect programmatically in the action method with:
FacesManager.instance().redirect(viewId, parameters, false);
OK, I managed to kill the redirect-filter by adding this to my compononents.xml:
<web:redirect-filter disabled="true" installed="false" />
However my main problem still exists, so that was apparently not the problem. It still adds the conversationId as an extra query string on the URL. Apperently I'm doing something wrong since it has been done before.
#Adeel Ansari:
components.xml:
<web:rewrite-filter view-mapping="/seam/*" />
pages.xml see my initial question.