I am using Spring and Thymeleaf. Thanks to xerx593, I was able to get it working so I updated this question to show the working code.
Here is my application class
package com.propfinancing.www;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
#Controller
#SpringBootApplication
public class PfWebApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(PfWebApplication.class, args);
}
#Bean
public LayoutDialect layoutDialect() {
return new LayoutDialect();
}
#GetMapping("/page1.html")
public String page1() {
return "page1";
}
}
Next, I create a layout.html file in src/main/resources/templates/layout.html
<!DOCTYPE html>
<html lang="en" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<body>
This is the layout template
<div layout:fragment="content">
<p>This is were the content will go</p>
</div>
</body>
</html>
And fin
ally, I created /ser/main/resources/templates/page1.html to use the template:
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}">
<body>
<div layout:fragment="content">
This is the content of page 1.
</div>
</body>
</html>
When I go to http://dev.propfinancing.com/www/page1.html, it gives me the template driven output I was expecting.
Thanks!
Neil
The most obvious mistake:
I created a simple html page called page1.html in my src/main/resources/static directory
(This is super for (spring-web) static content, but...)
And finally, I updated my page1.html to use the template...
Updating is not enough, you have to also move it to a configured template location! So moving the file to src/main/resources/templates/ (default location, issuing same browser request,) will hopefully/probably produce the desired result(, or at least throw an exception).
In short: src/main/resources/static directory is not intended for templates! (It can still be configured, but this would be very strange/hacky/bunch full of "side effects"!?).
Ok, the 404, can be fixed (simply) with:
#Controller // !
#SpringBootApplication
public class ThymeleafTestApplication {
public static void main(String[] args) {
SpringApplication.run(ThymeleafTestApplication.class, args);
}
#GetMapping("/page1.html") // !!
public String page1() {
return "page1"; // returning the view name
}
// ...
}
i.e. by providing a "controller" for this "view".
Or by configuring:
#SpringBootApplication
public class PfWebApplication // extends ...
implements WebMvcConfigurer {
#Override
public void addViewControllers (ViewControllerRegistry registry) {
ViewControllerRegistration r = registry.addViewController("/page1.html");
r.setViewName("page1");
// r.set...
}
...
One important thing is, that:
#Bean
public LayoutDialect layoutDialect() {
return new LayoutDialect();
}
is the "auto-configuration" approach, which equips us with "all the spring (boot) magic".
Whereas:
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addDialect(new LayoutDialect());
return templateEngine;
}
..is the "DIY" approach, and we'd have to tune (like e.g. spring-boot does).
Links:
https://ultraq.github.io/thymeleaf-layout-dialect/
https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web
https://www.thymeleaf.org/doc/articles/layouts.html
In my case the problem was in the use of decorator I solved this issue by simply changing decorator to decorate
Here I had the error when I used decorator to configure the created template using Thymeleaf
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="template1">
Then I changed the decorator keyword to decorate and it just worked fine :
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="template1">
Related
I am trying to learn Struts2, I have used a view page that hits action , the action class is using a bean where getter setter methods are written and the action is also using a dao where the connection is written.I want to mention that I am using jboss v5.0 and eclipse, I have added all the jar files.
Now , when I am trying to run this application the welcome jsp page is hitting properly then on clicking submit button the below error is showing:
There is no Action mapped for namespace /controller and action name
I am placing my code and directory structure. I have tried the using namespace="/"
<result name="SUCCESS">/success.jsp</result>, still its is not working
passing
+JAX-WS Web Services
+Deployment Descriptor:Passing
-Java Resources
-src
-controller
-TestAction.java
-TestAction
-execute(HttpServletRequest request , HttpServletResponse response) : String
-model
-TestBean.java
-TestBean
-age
-ocation
-name
-getAge() :String
-getName() :String
-getLocation() :String
-setAge(String) : void
-setName(String) : void
-getlocation(String) : void
-UserDao.java
- UserDao
-Logincheck() :Connection
registration.jsp
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>Passing</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>registration.jsp</welcome-file>
</welcome-file-list>
</web-app>
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="default" extends="sturts-default">
<action name="TestAction" class="controller.TestAction">
<result name="SUCCESS">/success.jsp</result>
</action>
</package>
</struts>
success.jsp
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<center> Welcome, Data successfully inserted</center>
</body>
</html>
registration.jsp
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%# taglib uri="/struts-tags" prefix="s"/%>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<s:form action="TestAction" method="post" >
<s:textfield name="Name" label="Name"/>
<s:textfield name="Age" label="Age"/>
<s:textfield name="Location" label="Location"/>
<s:submit label="Submit"></s:submit>
</s:form>
</body>
</html>
TestAction.java
package controller;
import java.sql.Connection;
import java.sql.PreparedStatement;
import com.opensymphony.xwork2.ActionSupport;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.TestBean;
import model.UserDao;
import org.apache.struts2.ServletActionContext;
public class TestAction extends ActionSupport{
public String execute(HttpServletRequest request , HttpServletResponse response) throws Exception
{
String Name = request.getParameter("Name");
String Age = request.getParameter("Age");
String Location = request.getParameter("Location");
TestBean bean = new TestBean();
bean.setName(Name);
bean.setAge(Age);
bean.setLocation(Location);
UserDao obj = new UserDao();
Connection x= obj.Logincheck();
if(x!=null)
{
PreparedStatement ps= x.prepareStatement("insert into Registration values(?,?,?)");
ps.setString(1, bean.getName());
ps.setString(2, bean.getAge());
ps.setString(3, bean.getLocation());
int i = ps.executeUpdate();
System.out.println("The value of i =" +i);
if(i>0)
{
return SUCCESS;
}
}
return null;
}
}
TestBean.java
package model;
public class TestBean {
String name;
String age;
String location;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
UserDao.java
package model;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class UserDao {
public Connection Logincheck()
{
Connection con=null;
try
{
try
{
Class.forName("oracle.jdbc.driver.OracleDriver");
}
catch(ClassNotFoundException e)
{
e.printStackTrace();
}
con= DriverManager.getConnection("jdbc:oracle:thin:#localhost:1521:XE", "username", "password");
if(con!=null)
{
return con;
}
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
}
that's all.
This error
There is no Action mapped for namespace /controller and action name
generally means Struts can't find the action specified in a namespace specified (/controller in this case). BTW in your case you've not specified /controller anywhere and the action name is missing in the error message, so this error could be probably generated by the bunch of other errors you have in your action / JSP / configuration.
you also have two typos, preventing anything to work:
sturts-default instead of struts-default:
<package name="default" extends="struts-default">
"SUCCESS" instead of "success", that is the result mapped to the constant SUCCESS that you are returning from the Action method.
<result name="success">/success.jsp</result>
If using Struts version newer than or equals to 2.1.3, you need the new filter, StrutsPrepareAndExecuteFilter instead of the deprecated FilterDispatcher;
The action method must be a public String something() with no parameters. This:
public String execute(HttpServletRequest request , HttpServletResponse response) throws Exception
is simply wrong, and should be:
public String execute(){}
Here
String Name = request.getParameter("Name");
String Age = request.getParameter("Age");
String Location = request.getParameter("Location");
TestBean bean = new TestBean();
bean.setName(Name);
bean.setAge(Age);
bean.setLocation(Location);
you are reinventing the wheel really badly. Attributes must be private, at class level, with getters and setters, and they'll be populated automatically, with no need to access the request. In your case:
private TestBean bean;
/* GETTER AND SETTER */
public String execute(){
System.out.println(bean.getName());
// ...
}
Unrelated, but Java naming conventions use lower-case names for local variables and instance properties.
In JSP page, you need to access the getter with a starting lowercase character:
<s:textfield name="Name" label="Name"/>
must be
<s:textfield name="name" label="Name"/>
and so the other fields. Also if you point to an object like TestBean, you need to do like this:
<s:textfield name="bean.name" label="Name"/>
This are the main problems... BTW consider start coding after having read a book, or some good tutorial. Starting in the dark like this is not the right way, IMHO.
Following are points which you should notice,
If you are using TestBean class as data carrier, you have to implement ModelDriven Interface.
TestAction.java should present in "Controller" package.
If you want HttpServletRequest and HttpServletResponse in your execute() method, then the simplest way you can get that is to implement ServletRequestAware and ServletResponseAware interfaces and implement respective methods.
And most important if your success.jsp is in Web Content folder, then you dont need to specify
<result name="SUCCESS">/success.jsp</result>
Just make it like
<result name="SUCCESS">success.jsp</result>
And if you have any folder suppose, "Folder1" in Web Content, Then specify like
<result name="SUCCESS">Folder1/success.jsp</result>
Im getting the next error when I try to load "sidepanel.jelly" in my Jenkins plugin action jelly file.
javax.servlet.ServletException: org.apache.commons.jelly.JellyTagException: file:/C:/Documents%20and%20Settings/Tecnoy/Escritorio/vats_eclipse/src/main/resources/org/jenkinsci/plugins/vats/VatsBuildAction/index.jelly:4:42: <st:include> No page found 'sidepanel.jelly' for class org.jenkinsci.plugins.vats.VatsBuildAction
at org.kohsuke.stapler.jelly.JellyClassTearOff.serveIndexJelly(JellyClassTearOff.java:117)
at org.kohsuke.stapler.jelly.JellyFacet.handleIndexRequest(JellyFacet.java:127)
at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:717)
at org.kohsuke.stapler.Stapler.invoke(Stapler.java:858)
at org.kohsuke.stapler.MetaClass$12.dispatch(MetaClass.java:390)
...
Caused by: org.apache.commons.jelly.JellyTagException: file:/C:/Documents%20and%20Settings/Tecnoy/Escritorio/vats_eclipse/src/main/resources/org/jenkinsci/plugins/vats/VatsBuildAction/index.jelly:4:42: <st:include> No page found 'sidepanel.jelly' for class org.jenkinsci.plugins.vats.VatsBuildAction
at org.kohsuke.stapler.jelly.IncludeTag.doTag(IncludeTag.java:124)
at org.apache.commons.jelly.impl.TagScript.run(TagScript.java:269)
at org.apache.commons.jelly.impl.ScriptBlock.run(ScriptBlock.java:95)
at org.kohsuke.stapler.jelly.CallTagLibScript$1.run(CallTagLibScript.java:99)
...
My jelly file has the following lines
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson">
<l:layout norefresh="true">
<st:include page="sidepanel.jelly" />
<l:main-panel>
<h1>Vats Summary:</h1>
<div id="canvas-holder">
<p><canvas id="chart-area" width="300" height="300"/></p>
</div>
<script type="text/javascript" src="${resURL}/plugin/vats/scripts/chart.min.js"></script>
<script type="text/javascript">
...
</script>
<canvas id="myChart" width="400" height="400"></canvas>
</l:main-panel>
</l:layout>
</j:jelly>
Any idea how can I fix it?
Thanks!
Add the <l:main-panel> tag and the the <l:layout norefresh="true">tag to the index.jelly file.
And include the side panel:
Pass the the build to Action (through a parameter of the constructor)
The build can be retrieved out of the parameters of the perform method which is inherited from the BuildStepCompatibilityLayer class (by Extending Publisher).
Create a getBuild() method in the Action class
Add the <st:include it="${it.build}" page="sidepanel.jelly" /> tag with the build
Jelly Example (index.jelly):
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<l:layout norefresh="true">
<st:include it="${it.build}" page="sidepanel.jelly" />
<l:main-panel>
<f:validateButton title="${%Restart Jenkins}" progress="${%Restarting...}" method="JksRestart" with="" />
</l:main-panel>
</l:layout>
</j:jelly>
Java Action class example:
package tryPublisher.tryPublisher;
import hudson.model.Action;
import hudson.model.AbstractBuild;
public class ExampleAction implements Action {
AbstractBuild<?,?> build;
public ExampleAction(AbstractBuild<?,?> build) {
this.build = build;
}
#Override
public String getIconFileName() {
return "/plugin/action.png";
}
#Override
public String getDisplayName() {
return "ExampleAction";
}
#Override
public String getUrlName() {
return "ExampleActionUrl";
}
public AbstractBuild<?,?> getBuild() {
return this.build;
}
}
Java Publisher class example:
package tryPublisher.tryPublisher;
import java.io.IOException;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
public class ExamplePublisher extends Publisher {
#Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
#Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws InterruptedException, IOException {
build.getActions().add(new ExampleAction(build));
return true;
}
}
The .jelly file has to be in the right resources map of the plugin project. In a map with the same name as the name of the Java class implementing Action. The name of the .jelly is important also.
I created an ejb
#Stateless
#LocalBean
public class BasitBean {
public String helloBasit() {
return "Basit";
} //end of helloBasit()
} //end of class BasitBean
I am calling it from JSF like
<h:body>
<h:outputLabel value="#{helloBasit.callBasit()}" />
</h:body>
#ManagedBean
#SessionScoped
public class HelloBasit {
#EJB
private BasitBean basitBean;
/** Creates a new instance of HelloBasit */
public HelloBasit() {
}
public String callBasit() {
return basitBean.helloBasit();
} //end of callBasit()
} //end of class HelloBasit
This code is working fine. But when i change the code like this
<h:body>
<h:outputLabel value="#{helloBasit.label}" />
</h:body>
#ManagedBean
#SessionScoped
public class HelloBasit {
#EJB
private BasitBean basitBean;
String label;
/** Creates a new instance of HelloBasit */
public HelloBasit() {
System.out.println();
String label = basitBean.helloBasit();
System.out.println(label);
}
public BasitBean getBasitBean() {
return basitBean;
}
public void setBasitBean(BasitBean basitBean) {
this.basitBean = basitBean;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
} //end of class HelloBasit
Then i get the exception
SEVERE: Error Rendering View[/index.xhtml]
com.sun.faces.mgbean.ManagedBeanCreationException: Cant instantiate class: pk.mazars.basitMahmood.HelloBasit.
at com.sun.faces.mgbean.BeanBuilder.newBeanInstance(BeanBuilder.java:193)
at com.sun.faces.mgbean.BeanBuilder.build(BeanBuilder.java:102)
......
Why i am getting this exception? The flow should be what i understand is when my page encounters #{helloBasit.label} then my constructor get call, instance variable get initialized, injected the bean instance into the basitBean, then the bean method should call. But i am getting null in the bean instance in this case why? Why previous code is working and it is not ? How can i call bean from the constructor ?
Thank you.
try to move your content of the constructor into a post constructor instead...
like this
#PostConstruct
private void init() {
System.out.println();
String label = basitBean.helloBasit();
System.out.println(label);
}
Cause the ejb bean should be injected only after the managed bean has been initiated
The #PostConstruct is being run after the constructor (after that the managed bean itself was created by the JSF) and only then the EJB is being injected into the bean and can be accessed...
Your idea is correct, but I see some things that may be fixed.
#LocalBean annotation is not required if your EJB is not directly implementing an interface. In this case, with or without the #LocalBean annotation you have the same effect. You may leave that if you want to make it explicit though. See this.
Make sure both #ManagedBean and #SessionScoped import from javax.faces.bean package.
Please, see this working sample:
EJB
import javax.ejb.Stateless;
#Stateless
public class PersonService {
public String getName() {
return "Cloud Strife";
}
}
Managed Bean
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
#ManagedBean
#SessionScoped
public class PersonBean {
#EJB
private PersonService ps;
private String name;
#PostConstruct
public void init() {
name = ps.getName();
}
public String getName() {
return name;
}
}
XHTML Page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<f:view contentType="text/html">
<h:head>
<title>Test</title>
</h:head>
<h:body>
<h1>Welcome, #{personBean.name}</h1>
</h:body>
</f:view>
</html>
If your value should be loaded only once, say at your bean construction, always prefer a method with #PostConstruct annotation instead of the constructor.
Also, in order to call bean methods before rendering a view you could use a f:event tag, for example:
<f:event type="preRenderView" listener="#{personBean.init}" />
I hope it helps!
In our JavaEE6 project (EJB3, JSF2) on JBoss 7.1.1, it seems we have a memory leak with SeamFaces #ViewScoped.
We made a little prototype to check the fact :
we use JMeter to call a page 200 times;
the page contains and calls a viewscoped bean which injects a stateful EJB;
we fix the session timeout at 1 minute.
At the end of the test, we check the content of the memory with VisualVM, and here what we got:
with a #ViewScoped bean, we still get 200 instances of the stateful MyController - and the #PreDestroy method is never called;
with a #ConversationScoped bean, #preDestroy method is called a the session end and then we got a clean memory.
Do we badly use the view scope, or is it truly a bug?
Here's the XHTML page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:s="http://jboss.org/seam/faces">
<f:metadata>
<f:viewParam name="u" value="#{myBean.uselessParam}" />
<s:viewAction action="#{myBean.callService}" />
</f:metadata>
<h:body >
<f:view>
</f:view>
</h:body>
</html>
Now the included bean myBean. For the #ConversationScoped variant, all commented parts are uncommented.
#ViewScoped
// #ConversationScoped
#Named
public class MyBean implements Serializable
{
#Inject
MyController myController;
//#Inject
//Conversation conversation;
private String uselessParam;
public void callService()
{
//if(conversation.isTransient())
//{
// conversation.begin();
//}
myController.call();
}
public String getUselessParam()
{
return uselessParam;
}
public void setUselessParam(String uselessParam)
{
this.uselessParam = uselessParam;
}
}
And then the injected stateful bean MyController:
#Stateful
#LocalBean
public class MyController
{
public void call()
{
System.out.println("call ");
}
#PreDestroy
public void destroy()
{
System.out.println("Destroy");
}
}
I see many developers are satisfied with #ViewAccessScoped in Myface CODI.
Could you please give it a try and tell the feedback.
I have faced the above mentioned problem in JSF managed #ViewScoped bean. After referring to few blogs I understood that JSF saves view bean states in http session and gets destroyed only when session is invalidated. Whenever we click on the jsf page every time new view scope bean referred in page is created. I did a work around using Spring Custom View Scope. It works fine. Below is the detail code.
For JSF 2.1:
Step 1: Create a View Scope Bean Post Construct Listener as follows.
public class ViewScopeBeanConstructListener implements ViewMapListener {
#SuppressWarnings("unchecked")
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
if (event instanceof PostConstructViewMapEvent) {
PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent) event;
UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
List<Map<String, Object>> activeViews = (List<Map<String, Object>>)
FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). get("com.org.jsf.activeViewMaps");
if (activeViews == null) {
activeViews = new ArrayList<Map<String, Object>>();
activeViews.add(viewRoot.getViewMap());
FacesContext.getCurrentInstance().getExternalContext().getSessionMap(). put("com.org.jsf.activeViewMaps", activeViews);
} else {
activeViews.add(viewRoot.getViewMap());
}
}
}
Step 2: Register event listener in faces-config.xml
<system-event-listener>
<system-event-listener-class>
com.org.framework.custom.scope.ViewScopeBeanConstructListener
</system-event-listener-class>
<system-event-class>javax.faces.event.PostConstructViewMapEvent</system-event-class>
<source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>
Step 3: Create a Custom View Scope bean as follows.
public class ViewScope implements Scope {
#Override
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
List<Map<String, Object>> activeViewMaps = (List<Map<String, Object>>)
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("com.org.jsf.activeViewMaps");
if (activeViewMaps != null && !activeViewMaps.isEmpty()
&& activeViewMaps.size() > 1) {
Iterator iterator = activeViewMaps.iterator();
if (iterator.hasNext()) {
Map<String, Object> oldViewMap = (Map<String, Object>)
iterator.next();
oldViewMap.clear();
iterator.remove();
}
}
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
Note : Other overridden methods can be empty.
For JSF 2.2:
JSF 2.2 saves the navigated view maps in http session in 'com.Sun.faces.application.view.activeViewMaps' as key. So add the below code in Spring Custom View Scope. No need of listeners as in JSF 2.1
public class ViewScope implements Scope {
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap =
FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
LRUMap lruMap = (LRUMap) FacesContext.getCurrentInstance().
getExternalContext().getSessionMap().get("com.sun.faces.application.view.activeViewMaps");
if (lruMap != null && !lruMap.isEmpty() && lruMap.size() > 1) {
Iterator itr = lruMap.entrySet().iterator();
while (itr.hasNext()) {//Not req
Entry entry = (Entry) itr.next();
Map<String, Object> map = (Map<String, Object>) entry.getValue();
map.clear();
itr.remove();
break;
}
}
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
Chances are this is a bug. Honestly the Seam 3 implementation wasn't all that great and the CODI one (and also what will be in DeltaSpike) is much better.
I'm using JBoss6.1.Final, JSF 2.0 (Mojarra), Weld CDI, MyFaces CODI 1.0.5 (for view-access-scoped)
I'm using something like the Gateway Pattern from Real World Java EE Patterns Rethinking Best Practices (unfortunately I don't have it with me, so I may have screwed something up here). Basically, the application allows a user to go into "edit mode" and edit a List of people (create, edit, remove) maintained in a #ViewAccessScoped backing bean with an extended persistence context and then click a "save" command link that flushes all their changes to the database. At first I was having a problem with ViewExpiredExceptions (if the browser was idle past the session-timeout period and then further requests are performed), but I added some jQuery to make a get request to a servlet that keeps the session alive (called 10 seconds before session-timeout). This seems to be working but now I have another problem, the backing bean is also a SFSB and after some idle time, it is being removed resulting in the following error message being logged (and all ajax rendered data disappears) when I attempt to perform more edits ...
13:06:22,063 SEVERE [javax.enterprise.resource.webcontainer.jsf.context] javax.el.ELException: /index.xhtml #27,81 rendered="#{!conversationBean.editMode}": javax.ejb.NoSuchEJBException: Could not find stateful bean: 43h1h2f-9c7qkb-h34t0f34-1-h34teo9p-de
Any ideas on how I could prevent SFSB removal or at least handle it more gracefully?
Here's my backing bean:
package com.ray.named;
import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.EJBTransactionRolledbackException;
import javax.ejb.Stateful;
import javax.ejb.TransactionAttribute;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ViewAccessScoped;
import com.ray.model.Person;
#Named
#Stateful
#ViewAccessScoped
#TransactionAttribute(javax.ejb.TransactionAttributeType.NEVER)
public class ConversationBean implements Serializable {
private static final long serialVersionUID = 1L;
//properties
private List<Person> people;
private String name;
private Boolean editMode;
#PersistenceContext(type=PersistenceContextType.EXTENDED)
private EntityManager em;
#PostConstruct
public void init() {
people = em.createNamedQuery("Person.findAll", Person.class).getResultList();
setEditMode(false);
}
//event listeners
public void beginEdits() {
setEditMode(true);
}
public void addPerson() {
Person p = new Person(name);
em.persist(p);
people.add(p);
name = null;
}
public void removePerson(Person p) {
people.remove(people.indexOf(p));
em.remove(p);
}
//this method flushes the persistence context to the database
#TransactionAttribute(javax.ejb.TransactionAttributeType.REQUIRES_NEW)
public void saveEdits() {
setEditMode(false);
}
//getters/setters
public List<Person> getPeople() {
return people;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getEditMode() {
return editMode;
}
public void setEditMode(Boolean editMode) {
this.editMode = editMode;
}
}
Here's the Person entity bean:
package com.ray.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Version;
#Entity
#NamedQueries({
#NamedQuery(name="Person.findAll",
query="SELECT p FROM Person p")
})
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
#Version
private int version;
public Person() { }
public Person(String name) {
setName(name);
}
public boolean equals(Object o) {
if (!(o instanceof Person)) {
return false;
}
return id == ((Person)o).id;
}
//getters/setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
Here's the view:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).ready(function() {
setInterval(function() {
$.get("#{request.contextPath}/poll");
}, #{(session.maxInactiveInterval - 10) * 1000});
});
</script>
<title>Conversation Test</title>
</h:head>
<h:body>
<h:form>
<h:commandLink value="Begin Edits" rendered="#{!conversationBean.editMode}">
<f:ajax render="#form" listener="#{conversationBean.beginEdits}"/>
</h:commandLink>
<h:commandLink value="Save" rendered="#{conversationBean.editMode}">
<f:ajax render="#form" listener="#{conversationBean.saveEdits}"/>
</h:commandLink>
<h:dataTable id="peopleTable" value="#{conversationBean.people}" var="person">
<h:column>
<f:facet name="header">Name</f:facet>
<h:panelGroup>
<h:inputText value="#{person.name}" disabled="#{!conversationBean.editMode}">
<f:ajax/>
</h:inputText>
<h:commandLink value="X" disabled="#{!conversationBean.editMode}">
<f:ajax render="#form" listener="#{conversationBean.removePerson(person)}"/>
</h:commandLink>
</h:panelGroup>
</h:column>
</h:dataTable>
<h:panelGrid columns="2">
<h:outputLabel for="name">Name:</h:outputLabel>
<h:inputText id="name" value="#{conversationBean.name}" disabled="#{!conversationBean.editMode}"/>
</h:panelGrid>
<h:commandButton value="Add" disabled="#{!conversationBean.editMode}">
<f:ajax execute="#form" render="#form" listener="#{conversationBean.addPerson}"/>
</h:commandButton>
</h:form>
</h:body>
</html>
Here's a servlet used to keep the session alive (called by jQuery ajax get request 10 seconds before session expires):
package com.ray.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PollServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void init() throws ServletException {
}
public String getServletInfo() {
return null;
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
request.getSession(); //Keep session alive
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
}
public void destroy() {
}
}
Any ideas on how I could prevent SFSB removal or at least handle it
more gracefully?
To investigate further I would recommend to take a look at the EJB lifecycle hooks for passivation and add some debug output there.
Should that be the source of the problem you will be able to configure / deactivate passivation - but scalability might come up as an issue.
Honestly, this scenario seems quite uncommon to me. In general I would expect requests / conversations / sessions to be working more or less in the default boundaries - should you find yourself writing code that circumvents this can it be that you are better off with a RESTful / stateless approach...?
Please update the question with further information if available.
I suppose you have already solved your problem. Otherwise, this JBoss wiki page should be helpful (also for future readers...).
https://community.jboss.org/wiki/Ejb3DisableSfsbPassivation
Cheers,
Luigi