📜 ⬆️ ⬇️

Unusual JSF and Spring integration

Introduction


I would like to share my experience with the unusual integration of these two frameworks. I really wouldn’t like to touch upon such extremely important issues “why do we need JSF at all”, I’ll make a reservation that I am not a supporter of this technology.

The zoo application for Spring + Hibernate + a large number of PL / SQL files and Oracle packages was developed for quite a long time. The user interface was created on ExtJS 4th and 2nd version, sometimes used self-made JavaScript and HTML. In general, normal corporate Frankenstein. Force majeure circumstances forced me to use JSF to create some part of the interfaces, so JSF should be integrated into the already existing query processing system based on Spring MVC. I used Primefaces, but I assume that the same methods apply for all other implementations.

Obvious things


First, create a separate servlet in the web.xml file.

<servlet> <servlet-name>JSF</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JSF</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> 

')
And in the faces-config.xml file, we add Spring support so that the xhtml objects can use the bins created by spring annotations.
 <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> </application> 


Wrapping up requests



Until now, everything is quite simple and described almost everywhere. Now we have to solve the following tasks:

  1. to provide transfer of get and post requests through spring to JSF.
  2. add transaction support (we have Hibernate inside with lazy collections)
  3. create bins with a lifetime of "view"
  4. prevent user access to xhtml files


The first two points can be solved simultaneously. In our application, all the URLs for which JSF interfaces will be created are of the form "/ app / * / jsf / *"

 @Controller("") @RequestMapping("/app/*/jsf") public class JSFTestController { @RequestMapping(value = "/*", method = {RequestMethod.GET}) @Transactional(rollbackFor = Exception.class) public void redirectToJSF(HttpServletRequest request, HttpServletResponse response) throws Exception { String uri = request.getRequestURI(); //       RequestContextHolder.getRequestAttributes().setAttribute(JSF_REQUEST_URL, uri, RequestAttributes.SCOPE_REQUEST); String xhtmlPath= getXHTMLPath(uri); //   xhml ,     //    ,    request.getRequestDispatcher(xhtmlPath).forward(request, response); } //   public static String getURLFromRequest(HttpServletRequest request) { return (String) RequestContextHolder.getRequestAttributes().getAttribute(JSF_REQUEST_URL, RequestAttributes.SCOPE_REQUEST); } 

The getXHTMLPath method returns the path to the xhtml file, for example /resources/jsf/accounts/find_create.xhtml to access the file that lies in [war file] \ resources \ jsf \ accounts \ find_create.xhtml.

This is enough to see the rendered xhtml page, however PrimeFaces is rich in POST requests for building an interface, and they clearly go to the wrong addresses. For the formation of the POST request path (that is, the form renderer is responsible for the value of the action attribute of the form). It is necessary to change only one static function getActionStr , unfortunately it was made static and it is necessary to block the function that calls it:

 public class ActCorrectFormRenderer extends FormRenderer { private static final com.sun.faces.renderkit.Attribute[] ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.FORMFORM); private boolean writeStateAtEnd; public ActCorrectFormRenderer() { WebConfiguration webConfig = WebConfiguration.getInstance(); writeStateAtEnd = webConfig.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.WriteStateAtFormEnd); } @Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { rendererParamsNotNull(context, component); if (!shouldEncode(component)) { return; } ResponseWriter writer = context.getResponseWriter(); assert (writer != null); String clientId = component.getClientId(context); // since method and action are rendered here they are not added // to the pass through attributes in Util class. writer.write('\n'); writer.startElement("form", component); writer.writeAttribute("id", clientId, "clientId"); writer.writeAttribute("name", clientId, "name"); writer.writeAttribute("method", "post", null); writer.writeAttribute("action", getActionStr(context), null); String styleClass = (String) component.getAttributes().get("styleClass"); if (styleClass != null) { writer.writeAttribute("class", styleClass, "styleClass"); } String acceptcharset = (String) component.getAttributes().get("acceptcharset"); if (acceptcharset != null) { writer.writeAttribute("accept-charset", acceptcharset, "acceptcharset"); } RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES); writer.writeText("\n", component, null); // this hidden field will be checked in the decode method to // determine if this form has been submitted. writer.startElement("input", component); writer.writeAttribute("type", "hidden", "type"); writer.writeAttribute("name", clientId, "clientId"); writer.writeAttribute("value", clientId, "value"); writer.endElement("input"); writer.write('\n'); // Write out special hhidden field for partial submits String viewId = context.getViewRoot().getViewId(); String actionURL = context.getApplication().getViewHandler().getActionURL(context, viewId); ExternalContext externalContext = context.getExternalContext(); String encodedActionURL = externalContext.encodeActionURL(actionURL); String encodedPartialActionURL = externalContext.encodePartialActionURL(actionURL); if (encodedPartialActionURL != null) { if (!encodedPartialActionURL.equals(encodedActionURL)) { writer.startElement("input", component); writer.writeAttribute("type", "hidden", "type"); writer.writeAttribute("name", "javax.faces.encodedURL", null); writer.writeAttribute("value", encodedPartialActionURL, "value"); writer.endElement("input"); writer.write('\n'); } } if (!writeStateAtEnd) { context.getApplication().getViewHandler().writeState(context); writer.write('\n'); } } private static String getActionStr(FacesContext context) { return JSFTestController.getURLFromRequest(HttpUtils.getCurrentRequest()); } } 


This is inconvenient, but there is no other way to change its static function. But in this way, we managed to change the action form and post requests will go to the same address as the get request, which built the view for the page. It remains to register your renderer in the faces-config file:

 <render-kit> ... <renderer> <component-family>org.primefaces.component</component-family> <renderer-type>org.primefaces.component.MenuRenderer</renderer-type> <renderer-class>com.XXXXX.ActCorrectMenuRenderer</renderer-class> </renderer> ... </render-kit> 


In the application, when processing GET and POST requests, slightly different checks are used, therefore the methods are separated, but for a simple example, the redirectToJSF method can be annotated with @RequestMapping (value = "/ *", method = {RequestMethod.GET, RequestMethod.POST})

"View" scope



JSF requires bins that exist within a view, i.e. are generated at every GET request and live at POST requests that originate from the page spawned by this GET request. We will not reinvent the wheel, we will borrow the code for the class here: forum.springsource.org/showthread.php?80595-View-scope-with-Spring

Security


The application had the ability to work under several users simultaneously from one browser, the session GUID was added to the URL, so a direct access to the xhtml file is blocked as unauthorized (the login form was made without JSF), and pull it out (or get JSF to process the user's request directly) using request with session GUID:

localhost: 8443 / <application name> /app/ea10efcb-4a2e-4eeb-aa71-24310882f7ad/jsf/accounts/find_create.xhtml

will not work, since no file [xxx.war] /app/ea10efcb-4a2e-4eeb-aa71-24310882f7ad/jsf/accounts/find_create.xhtml

Additionally, the following filters were installed:



Conclusion


Well, we reviewed the main points of a relatively painless (for the program, for the developer, many moments were extremely painful) adding JSF to a complex application. Behind the scenes, there are issues of handling exceptions during POST requests, combating chaotic component calls to bins to optimize performance, but this is a topic for a separate article.

Source: https://habr.com/ru/post/150857/


All Articles