How DefaultAnnotationHandlerMapping works - spring

I am confused about the way the DefaultAnnotationHandlerMapping works.
In my web.xml I have
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/somePath/someWork</url-pattern>
<url-pattern>/users</url-pattern>
<url-pattern>/user/*</url-pattern>
</servlet-mapping>
I have the controller like this,
#RequestMapping(value="/user/adduser", method={RequestMethod.POST})
public void addAdmin(#ModelAttribute("myData") myData data) {
System.out.println("We reached adduser controller");
}
And in the jsp file i have
<form:form id="adduser" method="post" action="/user/adduser" commandName="myData">
This does not work. I get the error no handler mapping found for "/adduser" and 404 for the page "/user/adduser"
But in the .xml file if i mention
<url-pattern>/user/adduser</url-pattern>
it works, or if i make the controller like,
#RequestMapping(value="/adduser", method={RequestMethod.POST})
also works. When submitting the page it reaches the correct controller.
I am now confused the way the #ReuqestMapping works. When a request comes like "/user/adduser" from where it will start looking for the right class and the right method?

Spring will match against the pathInfo property of the HttpServletRequest.
If your web.xml specifies <url-pattern>/user/*</url-pattern>, then the pathInfo will be the path with the /user prefix removed, so the #RequestMapping has to be /adduser.
If web.xml specifies <url-pattern>/user/adduser</url-pattern>, then the pathInfo will be the full /user/adduser path, so #RequestMapping has to match against that.
This isn't done by Spring, but by the servlet container, and it can be a bit confusing at times.
You can mitigate against this by using wildcards in #RequestMapping, e.g.
#RequestMapping(value="**/adduser", method={RequestMethod.POST})

Related

Influencing order of RequestMapping processing

In short this is what I'm searching for: I want to create a RequestMapping that catches all URLs except a small list I don't want to catch.
The minimum to exclude is everything below /webjars/, I also would like to exclude other URLS like /actuator/ and probably more.
Background information
We need to replace an old CMS which has literally thousands of different URLs. All URLs need to be detected and checked against a database and then the users shall be presented with a landing page, this landing page will then redirect the user to the new CMS target URL.
The logic that needs to be applied is too complicated for some Apache / nginx magic, therefore I wrote a Spring Boot application that can accomplish this.
I've created a #RequestMapping(value = "**", method = RequestMethod.GET) that catches all GET requests (these are the one I want to grab and react on) and a #RequestMapping(value = "**") for the other http verbs like POST and DELETE which I simply abort by sending a http error status code.
This works fine and the (simplified) code looks like this:
#RequestMapping(value = "**", method = RequestMethod.GET)
public String catchAccepted(HttpServletRequest request, HttpServletResponse httpServletResponse, Model model) {
model.addAttribute("targetUrl", ua.deriveNewUrl(request));
return "redirect";
}
#RequestMapping(value = "**")
#ResponseBody
public ResponseEntity<Object> catchDenied(HttpServletRequest request, HttpServletResponse httpServletResponse) {
return new ResponseEntity<Object>(HttpStatus.I_AM_A_TEAPOT);
}
The page that gets displayed for all the GET requests is based on a Thymeleaf template which uses Bootstrap in order to do the layout job.
In order to include Bootstrap I use the webjars-locator and org.webjars.bootstrap, the resources are included by specifying <script src="/webjars/bootstrap/js/bootstrap.min.js"></script> in the redirect.html Thymeleaf template.
Problem
The problem is, that my ** mapping on GET also gets applied to the /webjars/... call and instead of the desired js code I get whatever my redirect template returns.
I found no way to tell Spring Boot about the desired order.
First I would like to have Spring Boot handle the webjars mapping, then my other mapping.
Attempts so far
I checked other posts on SO but they only work when I have access to the sourcecode where the mapping is made. And I don't have access to the webjars locator / see no point in changing it just to solve this issue.
I also tried to set up a "anything that is not related to webjars" mapping like this:
#RequestMapping(value = "^(?!webjars$|actuator$).*", method = RequestMethod.GET)
But this doesn't have the desired effect because the RequestMapping only seems to support ant-stlye paths, which doesn't support negations because Ant (in contrast to Spring Boot) has a field for includes and excludes: https://ant.apache.org/manual/dirtasks.html
Negating the mapping seems only to be possible for params, not for the path: Change #RequestMapping order
I didn't yet find a way to influence the order if other mappings come from code I can not incluence.
But I found a way to configure "catch all except of ...":
#RequestMapping(value = { "", "/", "{url:(?!webjars$|actuator$).*}/**" }, method = RequestMethod.GET)
This configures three mappings. The first two are there to handle calls to the root of the webserver. The third configures a path pariable which I could also put into a #PathVariable but in my scenario the value doesn't matter. If you configure a path variable you need to give it a default because the pattern will only be satisfied depending on the value of your URL.
The regex tells Spring Boot only to react if the url doesn't contain webjars or actuator. The regex itself is best explained by using regex101:

Catch error when session timed out [duplicate]

I have written simple application with container-managed security. The problem is when I log in and open another page on which I logout, then I come back to first page and I click on any link etc or refresh page I get this exception. I guess it's normal (or maybe not:)) because I logged out and session is destroyed. What should I do to redirect user to for example index.xhtml or login.xhtml and save him from seeing that error page/message?
In other words how can I automatically redirect other pages to index/login page after I log out?
Here it is:
javax.faces.application.ViewExpiredException: viewId:/index.xhtml - View /index.xhtml could not be restored.
at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:212)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:312)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:343)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
at filter.HttpHttpsFilter.doFilter(HttpHttpsFilter.java:66)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:277)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97)
at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:325)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:226)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165)
at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791)
at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693)
at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954)
at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170)
at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57)
at com.sun.grizzly.ContextTask.run(ContextTask.java:69)
at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330)
at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309)
at java.lang.Thread.run(Thread.java:619)
Introduction
The ViewExpiredException will be thrown whenever the javax.faces.STATE_SAVING_METHOD is set to server (default) and the enduser sends a HTTP POST request on a view via <h:form> with <h:commandLink>, <h:commandButton> or <f:ajax>, while the associated view state isn't available in the session anymore.
The view state is identified as value of a hidden input field javax.faces.ViewState of the <h:form>. With the state saving method set to server, this contains only the view state ID which references a serialized view state in the session. So, when the session is expired or absent for one of the following reasons ...
session object is timed out in server
session cookie is timed out in client
session cookie is deleted in client
HttpSession#invalidate() is called in server
SameSite=None is missing on session cookie (and thus e.g. Chrome won't send them along when a 3rd party site (e.g. payment) navigates back to your site via a callback URL)
... then the serialized view state is not available anymore in the session and the enduser will get this exception. To understand the working of the session, see also How do servlets work? Instantiation, sessions, shared variables and multithreading.
There is also a limit on the amount of views JSF will store in the session. When the limit is hit, then the least recently used view will be expired. See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews.
With the state saving method set to client, the javax.faces.ViewState hidden input field contains instead the whole serialized view state, so the enduser won't get a ViewExpiredException when the session expires. It can however still happen on a cluster environment ("ERROR: MAC did not verify" is symptomatic) and/or when there's a implementation-specific timeout on the client side state configured and/or when server re-generates the AES key during restart, see also Getting ViewExpiredException in clustered environment while state saving method is set to client and user session is valid how to solve it.
Regardless of the solution, make sure you do not use enableRestoreView11Compatibility. it does not at all restore the original view state. It basically recreates the view and all associated view scoped beans from scratch and hereby thus losing all of original data (state). As the application will behave in a confusing way ("Hey, where are my input values..??"), this is very bad for user experience. Better use stateless views or <o:enableRestorableView> instead so you can manage it on a specific view only instead of on all views.
As to the why JSF needs to save view state, head to this answer: Why JSF saves the state of UI components on server?
Avoiding ViewExpiredException on page navigation
In order to avoid ViewExpiredException when e.g. navigating back after logout when the state saving is set to server, only redirecting the POST request after logout is not sufficient. You also need to instruct the browser to not cache the dynamic JSF pages, otherwise the browser may show them from the cache instead of requesting a fresh one from the server when you send a GET request on it (e.g. by back button).
The javax.faces.ViewState hidden field of the cached page may contain a view state ID value which is not valid anymore in the current session. If you're (ab)using POST (command links/buttons) instead of GET (regular links/buttons) for page-to-page navigation, and click such a command link/button on the cached page, then this will in turn fail with a ViewExpiredException.
To fire a redirect after logout in JSF 2.0, either add <redirect /> to the <navigation-case> in question (if any), or add ?faces-redirect=true to the outcome value.
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
or
public String logout() {
// ...
return "index?faces-redirect=true";
}
To instruct the browser to not cache the dynamic JSF pages, create a Filter which is mapped on the servlet name of the FacesServlet and adds the needed response headers to disable the browser cache. E.g.
#WebFilter(servletNames={"Faces Servlet"}) // Must match <servlet-name> of your FacesServlet.
public class NoCacheFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
res.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response);
}
// ...
}
Avoiding ViewExpiredException on page refresh
In order to avoid ViewExpiredException when refreshing the current page when the state saving is set to server, you not only need to make sure you are performing page-to-page navigation exclusively by GET (regular links/buttons), but you also need to make sure that you are exclusively using ajax to submit the forms. If you're submitting the form synchronously (non-ajax) anyway, then you'd best either make the view stateless (see later section), or to send a redirect after POST (see previous section).
Having a ViewExpiredException on page refresh is in default configuration a very rare case. It can only happen when the limit on the amount of views JSF will store in the session is hit. So, it will only happen when you've manually set that limit way too low, or that you're continuously creating new views in the "background" (e.g. by a badly implemented ajax poll in the same page or by a badly implemented 404 error page on broken images of the same page). See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews for detail on that limit. Another cause is having duplicate JSF libraries in runtime classpath conflicting each other. The correct procedure to install JSF is outlined in our JSF wiki page.
Handling ViewExpiredException
When you want to handle an unavoidable ViewExpiredException after a POST action on an arbitrary page which was already opened in some browser tab/window while you're logged out in another tab/window, then you'd like to specify an error-page for that in web.xml which goes to a "Your session is timed out" page. E.g.
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
Use if necessary a meta refresh header in the error page in case you intend to actually redirect further to home or login page.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Session expired</title>
<meta http-equiv="refresh" content="0;url=#{request.contextPath}/login.xhtml" />
</head>
<body>
<h1>Session expired</h1>
<h3>You will be redirected to login page</h3>
<p>Click here if redirect didn't work or when you're impatient.</p>
</body>
</html>
(the 0 in content represents the amount of seconds before redirect, 0 thus means "redirect immediately", you can use e.g. 3 to let the browser wait 3 seconds with the redirect)
Note that handling exceptions during ajax requests requires a special ExceptionHandler. See also Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request. You can find a live example at OmniFaces FullAjaxExceptionHandler showcase page (this also covers non-ajax requests).
Also note that your "general" error page should be mapped on <error-code> of 500 instead of an <exception-type> of e.g. java.lang.Exception or java.lang.Throwable, otherwise all exceptions wrapped in ServletException such as ViewExpiredException would still end up in the general error page. See also ViewExpiredException shown in java.lang.Throwable error-page in web.xml.
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
Stateless views
A completely different alternative is to run JSF views in stateless mode. This way nothing of JSF state will be saved and the views will never expire, but just be rebuilt from scratch on every request. You can turn on stateless views by setting the transient attribute of <f:view> to true:
<f:view transient="true">
</f:view>
This way the javax.faces.ViewState hidden field will get a fixed value of "stateless" in Mojarra (have not checked MyFaces at this point). Note that this feature was introduced in Mojarra 2.1.19 and 2.2.0 and is not available in older versions.
The consequence is that you cannot use view scoped beans anymore. They will now behave like request scoped beans. One of the disadvantages is that you have to track the state yourself by fiddling with hidden inputs and/or loose request parameters. Mainly those forms with input fields with rendered, readonly or disabled attributes which are controlled by ajax events will be affected.
Note that the <f:view> does not necessarily need to be unique throughout the view and/or reside in the master template only. It's also completely legit to redeclare and nest it in a template client. It basically "extends" the parent <f:view> then. E.g. in master template:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
and in template client:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
You can even wrap the <f:view> in a <c:if> to make it conditional. Note that it would apply on the entire view, not only on the nested contents, such as the <h:form> in above example.
See also
ViewExpiredException shown in java.lang.Throwable error-page in web.xml
Check if session exists JSF
Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request
Unrelated to the concrete problem, using HTTP POST for pure page-to-page navigation isn't very user/SEO friendly. In JSF 2.0 you should really prefer <h:link> or <h:button> over the <h:commandXxx> ones for plain vanilla page-to-page navigation.
So instead of e.g.
<h:form id="menu">
<h:commandLink value="Foo" action="foo?faces-redirect=true" />
<h:commandLink value="Bar" action="bar?faces-redirect=true" />
<h:commandLink value="Baz" action="baz?faces-redirect=true" />
</h:form>
better do
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
See also
When should I use h:outputLink instead of h:commandLink?
Difference between h:button and h:commandButton
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Have you tried adding lines below to your web.xml?
<context-param>
<param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
<param-value>true</param-value>
</context-param>
I found this to be very effective when I encountered this issue.
First what you have to do, before changing web.xml is to make sure your ManagedBean implements Serializable:
#ManagedBean
#ViewScoped
public class Login implements Serializable {
}
Especially if you use MyFaces
Avoid multipart forms in Richfaces:
<h:form enctype="multipart/form-data">
<a4j:poll id="poll" interval="10000"/>
</h:form>
If you are using Richfaces, i have found that ajax requests inside of multipart forms return a new View ID on each request.
How to debug:
On each ajax request a View ID is returned, that is fine as long as the View ID is always the same. If you get a new View ID on each request, then there is a problem and must be fixed.
I resolved this problem in JAVA EE 8 using AjaxExceptionHandler tag of Primefaces this is available from Primefaces 7 or higher (i am using and test in 11 version). Is so easy and you can combine this with a custom ExceptionHandlerWrapper as BalusC suggests. Use onShow event like this if you need that the page reload are auto.
<p:ajaxExceptionHandler type="javax.faces.application.ViewExpiredException"
update="viewExpiredDialog"
onexception="PF('viewExpiredDialog').show();" />
<p:dialog id="viewExpiredDialog" header="Session expired"
widgetVar="viewExpiredDialog" height="250px"
onShow="document.location.href = document.location.href;">
<h3>Reloading page</h3>
<p>Message...</p>
<!--Here, you decide that you need-->
<h:commandButton value="Reload" action="index?faces-redirect=true" />
Reload.
</p:dialog>
Add this configuration to faces-config.xml file. See ExceptionHandler and Error Handling
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
<application>
<el-resolver>
org.primefaces.application.exceptionhandler.PrimeExceptionHandlerELResolver
</el-resolver>
</application>
<factory>
<exception-handler-factory>
org.primefaces.application.exceptionhandler.PrimeExceptionHandlerFactory
</exception-handler-factory>
</factory>
</faces-config>
And VoilĂ  this works like clockwork. Regards.
You coud use your own custom AjaxExceptionHandler or primefaces-extensions
Update your faces-config.xml
...
<factory>
<exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...
Add following code in your jsf page
...
<pe:ajaxErrorHandler />
...
I was getting this error : javax.faces.application.ViewExpiredException.When I using different requests, I found those having same JsessionId, even after restarting the server.
So this is due to the browser cache. Just close the browser and try, it will work.
When our page is idle for x amount of time the view will expire and throw javax.faces.application.ViewExpiredException to prevent this from happening
one solution is to create CustomViewHandler that extends ViewHandler
and override restoreView method all the other methods are being delegated to the Parent
import java.io.IOException;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
public class CustomViewHandler extends ViewHandler {
private ViewHandler parent;
public CustomViewHandler(ViewHandler parent) {
//System.out.println("CustomViewHandler.CustomViewHandler():Parent View Handler:"+parent.getClass());
this.parent = parent;
}
#Override
public UIViewRoot restoreView(FacesContext facesContext, String viewId) {
/**
* {#link javax.faces.application.ViewExpiredException}. This happens only when we try to logout from timed out pages.
*/
UIViewRoot root = null;
root = parent.restoreView(facesContext, viewId);
if(root == null) {
root = createView(facesContext, viewId);
}
return root;
}
#Override
public Locale calculateLocale(FacesContext facesContext) {
return parent.calculateLocale(facesContext);
}
#Override
public String calculateRenderKitId(FacesContext facesContext) {
String renderKitId = parent.calculateRenderKitId(facesContext);
//System.out.println("CustomViewHandler.calculateRenderKitId():RenderKitId: "+renderKitId);
return renderKitId;
}
#Override
public UIViewRoot createView(FacesContext facesContext, String viewId) {
return parent.createView(facesContext, viewId);
}
#Override
public String getActionURL(FacesContext facesContext, String actionId) {
return parent.getActionURL(facesContext, actionId);
}
#Override
public String getResourceURL(FacesContext facesContext, String resId) {
return parent.getResourceURL(facesContext, resId);
}
#Override
public void renderView(FacesContext facesContext, UIViewRoot viewId) throws IOException, FacesException {
parent.renderView(facesContext, viewId);
}
#Override
public void writeState(FacesContext facesContext) throws IOException {
parent.writeState(facesContext);
}
public ViewHandler getParent() {
return parent;
}
}
Then you need to add it to your faces-config.xml
<application>
<view-handler>com.demo.CustomViewHandler</view-handler>
</application>
Thanks for the original answer on the below link:
http://www.gregbugaj.com/?p=164
Please add this line in your web.xml
It works for me
<context-param>
<param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name>
<param-value>true</param-value>
</context-param>
I ran into this problem myself and realized that it was because of a side-effect of a Filter that I created which was filtering all requests on the appliation. As soon as I modified the filter to pick only certain requests, this problem did not occur. It maybe good to check for such filters in your application and see how they behave.
I add the following configuration to web.xml and it got resolved.
<context-param>
<param-name>com.sun.faces.numberOfViewsInSession</param-name>
<param-value>500</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.numberOfLogicalViews</param-name>
<param-value>500</param-value>
</context-param>

sendRedirect in JSF 2.2

I am upgrading JSF from 1.2 to 2.2 version.
I have a simple response.sendRedirect() in my backing bean method. With JSF2.2, it started giving "java.lang.IllegalStateException: Cannot change buffer size after data has been written at org.apache.catalina.connecto" exception.
After adding "FacesContext.getCurrentInstance().responseComplete();", it worked!
Can anyone help me in understanding how the implementation is upgraded in JSF2.2 that redirect is not working without explicity saying response is completed?
Thanks!
You're supposed to use ExternalContext#redirect() for the job.
public void submit() throws IOException {
// ...
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "/otherpage.jsf");
}
This has always been the case since the beginning, also in JSF 1.x. It will under the covers automatically call FacesContext#responseComplete() after performing the HttpServletResponse#sendRedirect(). The responseComplete() will basically instruct JSF that the response is already manually completed, and that JSF basically doesn't need to proceed to render response phase then (i.e. writing the navigation outcome into the response).
Moreover, any attempt to grab and downcast the raw javax.servlet.* API from under JSF's covers should be taken as a hint to think twice if there isn't already a JSF-ish way to achieve the same. In JSF 2.x there's an additional new way to perform a redirect: append faces-redirect=true query parameter to the (implicit) navigation outcome:
public String submit() {
// ...
return "otherpage?faces-redirect=true";
}
As to the illegal state exception you faced, JSF 2.2 just postpones setting the response headers to the point when it actually needs to render response. It will be too late if the response is already committed.
Java.lang.IllegalStateException: Cannot change buffer size after data has been written at org.apache.catalina.connecto" exception.
This may occur because of you had set response buffer size manually for reducing memory reallocation at rendering time but your page has more size than buffer size
For example
<context-param>
<param-name> javax.faces.FACELETS_BUFFER_SIZE </param-name>
<param-value> 55555 </param-value>
</context-param>

Spring form and UTF-8 bad encoding

In our Web app we've faced a bad encoding problem. In order to reproduce this problem user selects in browser non unicode encoding(as example in chrome -> More tools->encoding->Koi8) and tries to set Cyrillic text.
Chars were spoiled when it goes to controller (just checked on debug) and even it's stored incorrectly and incorrectly rendered.
We've followed all recommendations: http://balusc.blogspot.com/2009/05/unicode-how-to-get-characters-right.html and seems this is a problem with submitting of application/x-www-form-urlencoded encoding content type. Because it's impossible to set charset during such forms submits.
As example if submit the same data using json and set necessry content type everything is stored correctly.
We've also tried example with this article:
http://www.codejava.net/frameworks/spring/spring-mvc-form-handling-tutorial-and-example and added additionally UTF8 filer with the following method:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
But the same problem was reproducible.
Could somebody suggest how to resolve mentioned problem?
Is it possible to correctly handle mentioned use case in Spring MVC because we tried on simple example and it seems it doesn't work. Does this use case of changing browser encoding valid at all ?
Try : In web.xml
<filter>
<filter-name>encoding-filter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Refer : http://wiki.apache.org/tomcat/FAQ/CharacterEncoding
Also, in view if you use jstl try to set the default encoding
This behavior can be achieved by using accept-charset="UTF-8" attribute in form.
It can be added in spring form tag. Also there's a bug in older versions of struts (1.1 are affected)
https://issues.apache.org/jira/browse/STR-1636
that makes impossible to add this attribute directly to the form. As a workaround jQuery can be used
jQuery( document ).ready(function() {
jQuery("#formSelector").attr("accept-charset", "UTF-8");
});
So in a nutshell this attribute will force browser send data from this form using specified encoding. In case user will set some control characters into the input they will be sent to backend as well so validation is required to prevent such cases. Also cases where the browser encoding and keyboard languages are not working well together will be handled (for example KOI8-U and Chinese keyboard language).
accept-charset official documentation
Somewhere in your request pipeline your overriding the encoding (ie String.getBytes() or new String(bytes) is being called with out the right encoding.
There are so many places that this can potentially happen and its one of the reasons why Spring Boot and various other frameworks force UTF-8 for both input and output particularly since UTF-8 is the recommended encoding.
Your users should not be changing the encoding. In fact when the page loads both the servlet response and the HTML itself should specify UTF-8 and this is for good reason. The server is saying "I speak UTF-8". If you want a different encoding you will have to specify a different encoding in the HTML (ie jsp), and the servlet request/responses so that the browser will then auto select encoding. Even then your mileage will vary for application/x-www-form-urlencoded particularly if you use Javascript (probably because the spec on the encoding in other characters is somewhat ambiguous). To give you some more example of why the HTML has to have the exact same encoding as what your sending over is that the name value pairs will have different encoding. That is your form has UTF-8 request parameter names (because thats whats in the HTML) but when you override it your supplying a different encoding for the parameter values (ie ?UTF-8Name=KoiValue&UTF-8Name=KoiValue). Hopefully you can see why that is bad and I'm not sure chrome is smart enough (or if it even should do it) to change the request parameter names back to Koi8.
Thus if you absolutely must support other character encoding you probably should use multipart/form (you specify this in the enctype attribute on the form element) AND NOT USE the encoding filters that set UTF-8 as those will probably cause corruption.

Mapping URL "names" to Controllers in JSPs

I'm wondering if there is a way in Spring MVC to map "url names" to URLS. For instance:
list_users -> /admin/users/list
edit_user?id=123 -> /admin/users/edit/123 (or /users/edit?id=123)
list_programs -> /admin/programs/list
I come from a PHP background(specifically Symfony), where each URL/action has a "route name", with which you can create URLs in your view templates. In Symfony, you would do something like this in your template:
{{ path('user_edit', { 'id' : 123} }}
//output = /admin/users/edit/123
URLs do not necessarily need to be RESTful, but I'm just wondering if something like this could be accomplished with Spring MVC.
Possible Solution that I've thought about:
Keeping urls listed in a properties/messages file, like:
url.users.edit=/admin/users/edit/{id}
url.users.list=/admin/users/list
and then using <spring:message code="url.users.list" /> or something like this to generate the URLs. Is this already a generally accepted solution? Is this perhaps bad practice?
The reason I'm asking, is it seems like bad practice to have full URLs spread out throughout JSPs. What happens if 25 URLs are under an "/admin" sub-directory, and I want to move them to a different sub-directory, say "/backend".
Forgive me if there is a duplicate post somewhere on this, or if there is a simple solution that I'm overlooking, but it is a hard topic find a specific answer for.
Thanks!
In case anyone runs into this post, I have found a solution for mapping names to URLs for Spring MVC:
https://github.com/resthub/springmvc-router
This solution gets the job done quite nicely, and I especially like the fact that all of your URLs / routes are able to be kept in a central location. This implementation renders the #RequestMapping annotation useless.
Please keep the following in mind:
Make sure your dispatcher servlet uses * as the URL parameter. Took me awhile to figure out that / alone will not work, and will cause URLs to be generated incorrectly.
<servlet-mapping>
<servlet-name>spring-dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Resources