I need to pass a bean object from MVC to webFlow. Currently, I am achieving it this way:
Storing my bean object as request attribute in controller.
Forwarding to flow.
Accessing the object from flowRequestContext on-start of my flow and setting it in flowScope.
#RequestMapping(value = "/ProcessUser", method=RequestMethod.POST)
public String processForm(LoginUser loginUser, HttpServletRequest request){
....
request.setAttribute("registrationDetails", registrationDetails);
return "forward:/chineseFlow"; //Call to flow
}
chineseFlow.xml
<on-start>
<evaluate expression="userDetailsService.getRegistrationDetails(flowRequestContext)" result="flowScope.registrationDetails"/>
</on-start>
UserDetailsService
public RegistrationDetails getRegistrationDetails(RequestContext requestContext){
HttpServletRequest httpRequest = (HttpServletRequest) requestContext.getExternalContext().getNativeRequest();
RegistrationDetails registrationDetails = (RegistrationDetails)httpRequest.getAttribute("registrationDetails");
return registrationDetails;
}
I don't want to pass multiple request parameters as input to my flow. Is this the correct way to pass the bean to SWF or is there any other better way to achieve the same?
There are not many options. Proper way would be to redesign your application so that whole process happens within the same flow, then you can store your values in flowscope to begin with. The only alternatives would be either a request attribute (which you are doing already), or session-scoped bean/session attribute. Out of these request attribute(s) is preferred as otherwise you will end up polluting your session scope, and introduce potential bugs that stem from leftover values in session scope.
Related
I'm trying to create a Thymeleaf dialect processor which performs a ServletDispatcher.include. I have extended the AbstractElementTagProcessor and overridden the doProcess method. The relevant code fragment is:
#Override
protected void doProcess(final ITemplateContext context, final IProcessableElementTag tag, final IElementTagStructureHandler structureHandler) {
ServletContext servletContext = null; // TODO: get servlet context
HttpServletRequest request = null; // TODO: get request
HttpServletResponse response = null; // TODO: get response
// Retrieve dispatcher to component JSP view
RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/something");
// Create wrapper (acts as response, but stores output in a CharArrayWriter)
CharResponseWrapper wrapper = new CharResponseWrapper(response);
// Run the include
dispatcher.include(request, wrapper);
String result = wrapper.toString();
// Create a model with the returned string
final IModelFactory modelFactory = context.getModelFactory();
final IModel model = modelFactory.parse(context.getTemplateData(), result);
// Instruct the engine to replace this entire element with the specified model
structureHandler.replaceWith(model, false);
I wrote similar code in the past in the form of a custom JSP tag. Problem is: I don't know how to access the ServletContext, HttpServletRequest and the HttpServletResponse!
Can this be done at all, or should I just accept that Thymeleaf is too good at hiding the HTTP context?
You can access request (by using #request object that gives you the direct access to javax.servlet.http.HttpServletRequest object) parameters and session (with #session object that gives you direct access to the javax.servlet.http.HttpSession object) attributes directly in Thymeleaf views:
${#request.getAttribute('foo')}
${#request.getParameter('foo')}
${#request.getContextPath()}
${#request.getRequestName()}
<p th:if="${#request.getParameter('yourParameter') != null
th:text="${#request.getParameter('yourParameter')}"}">Request Param</p>
${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}
<p th:if="${session != null}"> th:text="${session.yourAttribute}"</p>
Read more here.
I found myself with a very similar requirement of accessing the request from an implementation of IExpressionObjectFactory.
The way i solved it (following #Sebastian Marsching advise in a previous comment) is by accessing the objects registered in IExpressionContext that are available from the view in the context of template evaluation (all those objects described in Appendix A and Appendix B of thymeleaf documentation), so you have access to request, response, servletContext and many other utility objects.
Speaking in code:
IExpressionObjects expressionObjects = context.getExpressionObjects();
HttpServletRequest request = (HttpServletRequest)expressionObjects.getObject("request");
There is also an expressionObjects.getObjectNames() method you can call to get a Set<String> with the names of all registered objects, which in my case gives the following list:
[i18nutils, ctx, root, vars, object, locale, request, response, session,
servletContext, conversions, uris, calendars, dates, bools, numbers, objects,
strings, arrays, lists, sets, maps, aggregates, messages, ids, execInfo,
httpServletRequest, httpSession, fields, themes, mvc, requestdatavalues]
Is it possible to write just a single attribute to the original session without using <private-session-attributes>false</private-session-attributes> with Liferay 6.2.10 and Liferay-Faces-Bridge 3.2.4?
In a JSF-bean / portlet we configure a export file that must be downloadable via a servlet (inside the same WAR).
We want to share one specific Object via the session to get used by some JSTL-magic inside the portal.
I have found no other way than setting <private-session-attributes>false</private-session-attributes>, but that pollutes the session with lots of JSF-specific and even more portlet-specific objects that no one needs in the user-global session. As most portlets in that war need to communicate I would either have to switch all to public session attributes or use IPC.
I tried several ways that only yield positive results while not using private session attributes.
ServiceContextThreadLocal.getServiceContext().getRequest().getSession().setAttribute("SERVICE_CONTEXT", true);
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
// Does not matter which way
// PortletSession portletSession = (PortletSession)externalContext.getSession(false);
PortletSession portletSession = ((PortletRequest) externalContext.getRequest()).getPortletSession();
portletSession.setAttribute("PORTLET_SESSION_PORTLET_SCOPE", true, PortletSession.PORTLET_SCOPE);
portletSession.setAttribute("PORTLET_SESSION_APPLICATION_SCOPE", true, PortletSession.APPLICATION_SCOPE);
HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest((PortletRequest) externalContext.getRequest());
httpServletRequest.getSession().setAttribute("EXTERNAL_CONTEXT_SERVLET_REQUEST_SESSION", true);
HttpServletRequest outerRequest = PortalUtil.getOriginalServletRequest(httpServletRequest);
outerRequest.getSession().setAttribute("EXTERNAL_CONTEXT_SERVLETS_SERVLET_REQUEST", true);
Other options that I would like to avoid would be:
use a javax.servlet.Filter with a ThreadLocal
save the generated document (or export configuration) to a database
transport the configuration via the client by re-posting it to the export servlet.
This answer suggests to use the portletSession with ApplicationScoped variables, but I couldn't get the PortletSession.
With setting <private-session-attributes>false</private-session-attributes> I get the following attributes set in the original session:
TEST_WITH_EXTERNAL_CONTEXT_SERVLET_REQUEST_SESSION
TEST_WITH_PORTLET_SESSION_APPLICATION_SCOPE
TEST_WITH_SERVICE_CONTEXT
war_app_name_whatever?TEST_WITH_PORTLET_SESSION_PORTLET_SCOPE
and a great number of other objects (>50) visible in the global users session.
Has anyone a good idea how to set just one session attribute?
Unwrapping the request until reaching a class that does not extend javax.servlet.http.HttpServletRequestWrapper solves the problem.
The request is kindly stored by Liferay and available via ServiceContextThreadLocal.getServiceContext().getRequest().
Liferays PortalUtil just unwraps if the request wrapper is in a package that starts with "com.liferay." and therefore does not work if a custom request wrapper is used.
public static <Type, ValueType extends Type> void setOnOriginalSession(Class<Type> type, ValueType value) {
HttpServletRequest request = ServiceContextThreadLocal.getServiceContext().getRequest();
HttpServletRequest originalRequest = unwrapOriginalRequest(request);
HttpSession originalSession = originalRequest.getSession();
String attributeNameForType = getAttributeNameForType(type);
originalSession.setAttribute(attributeNameForType, value);
}
private static HttpServletRequest unwrapOriginalRequest(HttpServletRequest request) {
while (request instanceof HttpServletRequestWrapper) {
HttpServletRequestWrapper httpServletRequestWrapper = (HttpServletRequestWrapper) request;
request = (HttpServletRequest) httpServletRequestWrapper.getRequest();
}
return request;
}
I have some questions from a design point of view in Spring Web MVC.
Is it good practice to use Request Object in controller? If not, then what is alternative way to pass pass one text fields value to controller? Do I need to create one new from bean for this single fields?
It depends of the situation, in a few cases I used the HttpServletRequest; for example for writing a file to the output stream.
If you want to get the Request Parameters you can use the annotation #RequestParam, that it´s more easy to get the parameters from the request.
Depends that you want to handle, for example for a form you can use #ModelAttribute and this attribute can be in a session or in the request.
For example:
#Controller
public class YourController {
#RequestMapping(value = "someUrl", method = RequestMethod.GET)
public String someMethod(#RequestParam("someProperty") String myProperty)
{
// ... do some stuff
}
}
Check the documentation here:
#RequestParam
#ModelAttribute
#PathVariable
As Spring Specification said, #ModelAttribute will executed before the mapping handler and #SessionAttribute will keep the model attribute in session.
Consider below scenario: form bean is created after the controller is called and is set as session attribute as well. Next time MenuController is called, createForm() will be executed again and create another new form bean. My question is: will this latest created form bean be set as session attribute? and which form bean will be bind to the parameter in method bookList()?
Hope you guys can help. Thank you.
#Controller
#RequestMapping("/store")
#SessionAttribute("form")
public class MenuController {
#ModelAttribute("form")
public Form createForm() {
return new Form();
}
#RqeustMapping("/book")
public String bookList(#ModelAttribute("form") Form form){
//processing the form
}
}
When the bookList method is invoked for the first time in a given session, then method with #ModelAttribute('form) is invoked, the returned value (Form object) is stored in HttpSession and finally the bookList method is invoked with the same Form object passed as an argument (obtained from session).
For the subsequent requests within the same HttpSession, Spring retrieves the same Form object from the session and doesn't call the method with #ModelAttribute('form') again till the end of the session.
After each end of the bookList method invocation Spring stores updated version of Form object in HttpSession.
If you are using Spring Boot 2.x you can debug DefaultSessionAttributeStore#retrieveAttribute method to understand this behaviour.
Remember that your mapping is generalised. It will map both to a GET method and a POST method.
If your request mapping is a GET method,
The session attribute will hold the value of the #ModelAttribute("form") from the method createForm.
If an attribute form is returned from a POST request,
The session Attribute will override the #Model Attribute from the createForm method.
It is helpful to remember that the #ModelAttribute will execute before the mapping handler.
the sessionAttribute indicates that the "form" will be saved in the session. not meaning the "form" is retrieved from the session.
I have a Spring MVC controller with an action that's called using AJAX.
#SessionAttributes({"userContext"})
public class Controller
{
...
#RequestMapping(value = "/my-url", method= { RequestMethods.POST })
public ModelAndView doSomething(#ModelAttribute("userContext") UserContext context,
SessionStatus sessionStatus)
{
BusinessObject obj = doSomeBusinessLogic(context.getUserName());
sessionStatus.setComplete();
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("someInt", obj.getId());
return mav;
}
}
When I run this action, I get the following exception:
net.sf.json.JSONException: There is a cycle in the hierarchy!
at t.sf.json.util.CycleDetectionStrategy$StrictCycleDetectionStrategy.handleRepeatedReferenceAsObject(CycleDetectionStrategy.java:97)
at net.sf.json.JSONObject._fromBean(JSONObject.java:833)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at org.springframework.web.servlet.view.json.writer.jsonlib.PropertyEditorRegistryValueProcessor.processObjectValue(PropertyEditorRegistryValueProcessor.java:127)
at net.sf.json.JSONObject._fromMap(JSONObject.java:1334)
Truncated. see log file for complete stacktrace
After doing some debugging I found out that Spring is placing the UserContext object onto the ModelAndView that I am returning. If I hard-code my user name and remove the context object from the method's parameters, the action runs successfully. Is there a way to configure Spring to omit the ModelAttribute-annotated parameters from the returned ModelAndView? As you can see, sessionStatus.setComplete() has no effect.
I've had similar problems in the past with #SessionAttributes. By declaring #SessionAttributes({"userContext"}) you're telling Spring that you want "userContext" to always be available in the model, and so Spring has no choice but to send your UserContext object out to the model, just in case you're going to be redirecting or doing something else which might end up at another Controller.
The "solution" (and I didn't like it much, but it worked) was to omit the #SessionAttributes annotation on the controller, add an HttpSession parameter to the necessary methods and "manually" manage what's in it.
I'm interested to see if there's a better way, because it seems #SessionAttributes has tremendous potential to tidy up controller-level code.
I registered a WebArgumentResolver to get to my session variable. This allowed me to keep this session variable out of the response while keeping my action unit testable.
Along with #ModelAttribute, pass #ModelMap as a method argument.
Based on business logic, error conditions -- if you do not need the attribute for certain scenarios, then remove it from the map.
public ModelAndView foo(#ModelAttribute("userContext") UserContext, #ModelMap map){
if(success){
return success.jsp
}
else{
map.remove("userContext");
return "error.jsp"
}
}
Not totally satisfied with having to pass the ModelMap as well, but I did not find any other easier way of doing it.
Cheers!!