The view and bean were working until I tried to fix non-standard names, and I've now broken the connection between the two. Oddly, the "back" button has the correct link, but content just doesn't show, nor log. Why doesn't Detail.getComments() execute?
I've been going through the weld docs and trying to better understand #Inject. There seems to be a lifecycle problem which I don't understand, either. If it's not lifecycle, then I cannot even speculate as to why Detail.getComments() never shows in the glassfish logs:
INFO: MessageBean.getModel..
INFO: SingletonNNTP.returning messages..
INFO: MessageBean.getModel..
INFO: SingletonNNTP.returning messages..
INFO: MessageBean.getModel..
INFO: SingletonNNTP.returning messages..
INFO: Detail..
INFO: Detail.getId..null
INFO: Detail.getId..SETTING DEFAULT ID
INFO: Detail.onLoad..2000
INFO: Detail.getId..2000
INFO: Detail.getId..2000
INFO: Detail.setId..2000
INFO: Detail.getId..2019
INFO: ..Detail.setId 2019
INFO: Detail.back..
INFO: Detail.getId..2019
INFO: ..Detail.back 2,018
INFO: Detail.getId..2019
The value 2000 is a default, which only happens when id==null, which it never should. It should pull in that attribute right away. So, I'm not sure whether that's a problem with the scope (I only just now found out that CDI doesn't support #SessionScoped), the lifecycle, or something else. Perhaps I need to use #Inject on that variable?
The view, detail.xhtml:
<?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:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<f:metadata>
<f:viewParam name="id" id="id" value="#{detail.id}" />
</f:metadata>
<ui:composition template="./complexTemplate.xhtml">
<ui:define name="top">
<h:link value="back" outcome="detail" includeViewParams="true">
<f:param name="id" value="#{detail.back}"/>
</h:link>
<ui:define name="content">
<h:outputText value="#{detail.content}" rendered="false"/>
</ui:define>
<ui:define name="bottom">
bottom
</ui:define>
</ui:composition>
</body>
</html>
and the backing bean:
package net.bounceme.dur.nntp;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Named;
import javax.mail.Message;
#Named
#ConversationScoped
public class Detail implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(Detail.class.getName());
private static final Level level = Level.INFO;
private String id = null; //should never get default value in getter
private Message message = null;
private SingletonNNTP nntp = SingletonNNTP.INSTANCE;
private String forward = null; //id + 1
private String back = null; //id - 1
private String content = null; //message.content
public Detail() {
logger.log(level, "Detail..");
}
#PostConstruct
private void onLoad() {
logger.log(level, "Detail.onLoad..{0}", getId());
}
public Message getMessage() {
logger.log(level, "Detail.getMessage..");
return message;
}
public void setMessage(Message message) {
logger.log(level, "Detail.setMessage..");
this.message = message;
}
public String getId() {
logger.log(level, "Detail.getId..{0}", id);
if (id == null) {
logger.log(level, "Detail.getId..SETTING DEFAULT ID");
id = String.valueOf(2000);
}
return id;
}
public void setId(String id) throws Exception {
logger.log(level, "Detail.setId..{0}", getId());
this.id = id;
logger.log(level, "..Detail.setId {0}", getId());
}
public String getForward() {
logger.log(level, "Detail.forward..");
int f = Integer.parseInt(getId());
f = f + 1;
logger.log(level, "..Detail.forward {0}", f);
forward = String.valueOf(f);
return forward;
}
public void setForward(String forward) {
this.forward = forward;
}
public String getBack() {
logger.log(level, "Detail.back..");
int b = Integer.parseInt(getId());
b = b - 1;
logger.log(level, "..Detail.back {0}", b);
back = String.valueOf(b);
return back;
}
public void setBack(String back) {
this.back = back;
}
public String getContent() throws Exception {
logger.log(level, "Detail.getContent..{0}", getId());
message = nntp.getMessage(Integer.parseInt(getId()));
content = message.getContent().toString();
return content;
}
public void setContent(String content) {
this.content = content;
}
}
which never seems to have, according to the above logs, Detail.getContent() invoked, despite that being part of the view: <h:outputText value="#{detail.content}" rendered="false"/>
It's odd in that Detail.content() was getting invoked prior to my changing this class to better follow naming conventions. I'm going through some Weld and Oracle Java EE 6 docs, but don't at all mind being directed to a fine manual. The docs I find describing this are invariably using #ManagedBeans, however, which I am not. There seem many gotchas, as described in this answer by #Kawu.
Adding #Inject to the id field causes a deploy error:
init:
deps-module-jar:
deps-ear-jar:
deps-jar:
library-inclusion-in-archive:
library-inclusion-in-manifest:
compile:
compile-jsps:
In-place deployment at /home/thufir/NetBeansProjects/NNTPjsf/build/web
Initializing...
deploy?DEFAULT=/home/thufir/NetBeansProjects/NNTPjsf/build/web&name=NNTPjsf&contextroot=/NNTPjsf&force=true failed on GlassFish Server 3.1.2
Error occurred during deployment: Exception while loading the app : WELD-001408 Unsatisfied dependencies for type [String] with qualifiers [#Default] at injection point [[field] #Inject private net.bounceme.dur.nntp.Detail.id]. Please see server.log for more details.
/home/thufir/NetBeansProjects/NNTPjsf/nbproject/build-impl.xml:749: The module has not been deployed.
See the server log for details.
BUILD FAILED (total time: 9 seconds)
Surely, injecting a String isn't the problem, perhaps it's a bug.
I understand your frustration, and I see that the problem is more your setup / understanding in general. But still, it's pretty hard to find any real questions to answer, maybe you can try to split your problems next time.
Here are some answers:
Why doesn't Detail.getComments() execute?
Hm, maybe because it's not in the bean? I guess that you are refrerring to detail.getContent instead?
which never seems to have, according to the above logs,
Detail.getContent() invoked, despite that being part of the view:
Try rendered = true :-)
#PostConstruct
private void onLoad() {
logger.log(level, "Detail.onLoad..{0}", getId());
}
You've put an awful lot of logic into the getter. Try debugging with the field, not with the getter...
The value 2000 is a default, which only happens when id==null, which it never should.
It looks like private String id = null; is a perfect explanation why id will be null.
Try to keep in mind that modern frameworks like JSF, CDI and Java EE do a lot of stuff behind the scenes, using reflection, proxies and interceptors. Don't rely on classical understanding of when (and how often) a constructor is called, for example.
Again, consider moving your initialisation logic away from the getter. #PostConstruct would be the place that the fathers of the Java EE-spec had chosen for it.
To be honest: Nothing looks extremely wrong, but your code is kind of messy, and extremely hard to understand and to follow.
Try removing all indirections like this one...
int b = Integer.parseInt(getId());
... and everything will look much better.
Oh, and is there a specific reason why you declare a fixed log-level for the whole class? Try something like this
private static final Logger LOG = Logger.getLogger(Some.class);
...
LOG.info("...");
Hope that gives you a start. Feel free to post further questions, preferably a bit shorter and with single, isolated aspects to answer.
Related
Basically my question is the same as this one answered five years ago: I would like to output the URL of a server-provided image as from <p:graphicImage>, but just the URL and not an <img> tag. What I am looking for is a solution like this:
(This is not working code, just to illustrate:)
<p:graphicImage var="url" value="#{MyForm.image}"/>
<span id="imageData" data-image="#{url}/>
Which should output to the HTML like this:
<span id="imageData" data-image="http://localhost:8080/contextPath/javax.faces.resource/dy...
I am currently using a JavaScript work-around for this, but this requirement seems so common to me that I would expect newer development has already targeted it and there is a built-in solution. Is there news on that topic since the last five years?
Just took a look at the GraphicImageRenderer wanting to figure out how they produce that dynamic URL. They are using a static method on their DynamicContentSrcBuilder:
protected String getImageSrc(FacesContext context, GraphicImage image) throws Exception {
String name = image.getName();
if (name != null) {
// ...
} else {
return DynamicContentSrcBuilder.build(context, image.getValue(), image, image.isCache(), DynamicContentType.STREAMED_CONTENT, image.isStream());
}
}
So why not just reproduce this call on the p:graphicImage component?
I'll first bind it to a property of a RequestScoped bean and then get the URI from that bean which reproduces behavior of the renderer shown above:
The XHTML code:
<!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://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
<h:head/>
<body>
<p:graphicImage binding="#{myBean.imgComponent}"
value="#{myBean.img}" />
<h:outputText id="imageData" pt:data-image="#{myBean.getImgUri()}" />
</body>
</html>
And my bean aka myBean:
package test;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import org.primefaces.application.resource.DynamicContentType;
import org.primefaces.component.graphicimage.GraphicImage;
import org.primefaces.model.StreamedContent;
import org.primefaces.util.DynamicContentSrcBuilder;
#Named
#RequestScoped
public class MyBean {
private GraphicImage imgComponent;
private String imgUri;
public StreamedContent getImg() throws IOException {
// ...
}
public GraphicImage getImgComponent() {
return imgComponent;
}
public void setImgComponent(GraphicImage imgComponent) {
this.imgComponent = imgComponent;
}
public String getImgUri() throws UnsupportedEncodingException {
assert null != imgComponent;
if (null == imgUri) {
// here we reproduce the GraphicImageRenderer behavior:
imgUri = DynamicContentSrcBuilder.build(FacesContext.getCurrentInstance(), imgComponent.getValue(),
imgComponent, imgComponent.isCache(), DynamicContentType.STREAMED_CONTENT, imgComponent.isStream());
}
return imgUri;
}
}
Alternatively we can use the GraphicImageRenderer and publish the protected method getImageSrc:
public class MyBean {
// ...
public String getImgUri() throws Exception {
assert null != imgComponent;
if (null == imgUri) {
imgUri = new GraphicImageRendererXtension().getPublicImageSrc(FacesContext.getCurrentInstance(),
imgComponent);
}
return imgUri;
}
}
public class GraphicImageRendererXtension extends GraphicImageRenderer {
// publish the protected GraphicImageRenderer#getImageSrc method:
public String getPublicImageSrc(FacesContext context, GraphicImage image) throws Exception {
return getImageSrc(context, image);
}
}
But why did I write #{myBean.getImgUri()} instead of #{myBean.imgUri}? I'm still not sure, but using the latter produces a PropertyNotFoundException:
javax.el.PropertyNotFoundException: /myView.xhtml #20,46 value="#{myBean.imgUri}": ELResolver did not handle type: [null] with property of [imgUri]
at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:117)
at javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:200)
at javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:187)
at javax.faces.component.UIOutput.getValue(UIOutput.java:179)
at com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer.getValue(HtmlBasicInputRenderer.java:205)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.getCurrentValue(HtmlBasicRenderer.java:360)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd(HtmlBasicRenderer.java:171)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:949)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1912)
at javax.faces.render.Renderer.encodeChildren(Renderer.java:176)
at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:918)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeRecursive(HtmlBasicRenderer.java:309)
at com.sun.faces.renderkit.html_basic.GroupRenderer.encodeChildren(GroupRenderer.java:114)
at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:918)
at org.primefaces.renderkit.CoreRenderer.renderChild(CoreRenderer.java:86)
at org.primefaces.renderkit.CoreRenderer.renderChildren(CoreRenderer.java:73)
at org.primefaces.component.layout.LayoutUnitRenderer.encodeEnd(LayoutUnitRenderer.java:49)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:949)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1912)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1908)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1908)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1908)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:491)
at javax.faces.view.ViewDeclarationLanguageWrapper.renderView(ViewDeclarationLanguageWrapper.java:126)
...
Caused by: javax.el.PropertyNotFoundException: ELResolver did not handle type: [null] with property of [imgUri]
at org.apache.el.parser.AstValue.getValue(AstValue.java:174)
at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:190)
at org.apache.webbeans.el22.WrappedValueExpression.getValue(WrappedValueExpression.java:68)
at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:115)
... 122 more
Possibly invoking the DynamicContentSrcBuilder.build() method somehow messes up the ongoing resolving of the value expression #{myBean.imgUri}, while #{myBean.getImgUri()} is not affected.
Getting back to your question: no there is no news. The dynamic URI is built and directly sent to the browser while rendering. The GraphicImage component does not provide any getter for this. What I presented here is a server side variant of a work around without the need to create a new servlet.
Edit:
Of course you can also parameterize the getImageUri method and bind your Image component to some arbitrary variable which would bring you closer to your demonstration var="..." example:
public class MyBean {
// ...
public String getImgUri(GraphicImage imgComponent) throws Exception {
assert null != imgComponent;
String imgUri = new GraphicImageRendererXtension().getPublicImageSrc(FacesContext.getCurrentInstance(),
imgComponent);
return imgUri;
}
}
Then use this method by:
<p:graphicImage binding="#{myImage}" value="#{myBean.img}" />
<h:outputText id="imageData" pt:data-image="#{myBean.getImgUri(myImage)}" />
This way there's no need to create a bean or method for each image.
I would like to pass an value to a managed bean under the hood. So I have this managed bean:
#ManagedBean(name = "mbWorkOrderController")
#SessionScoped
public class WorkOrderController {
// more attributes...
private WorkOrder workOrderCurrent;
// more code here...
public WorkOrder getWorkOrderCurrent() {
return workOrderCurrent;
}
public void setWorkOrderCurrent(WorkOrder workOrderCurrent) {
this.workOrderCurrent = workOrderCurrent;
}
}
It holds a parameter workOrderCurrent of the custom type WorkOrder. The class WorkOrder has an attribute applicant of type String.
At the moment I am using a placeholder inside my inputtext to show the user, what he needs to type inside an inputText.
<p:inputText id="applicant"
value="#{mbWorkOrderController.workOrderCurrent.applicant}"
required="true" maxlength="6"
placeholder="#{mbUserController.userLoggedIn.username}" />
What I want to do, is to automatically pass the value of mbUserController.userLoggedIn.username to mbWorkOrderController.workOrderCurrent.applicant and remove the inputText for applicant completely from my form.
I tried to use c:set:
<c:set value="#{mbUserController.userLoggedIn.username}" target="#{mbWorkOrderController}" property="workOrderCurrent.applicant" />
But unfortunatelly I get a javax.servlet.ServletException with the message:
The class 'WorkOrderController' does not have the property 'workOrderCurrent.applicant'.
Does anybody have an advice?
The class 'WorkOrderController' does not have the property 'workOrderCurrent.applicant'.
Your <c:set> syntax is incorrect.
<c:set value="#{mbUserController.userLoggedIn.username}"
target="#{mbWorkOrderController}"
property="workOrderCurrent.applicant" />
You seem to be thinking that the part..
value="#{mbWorkOrderController.workOrderCurrent.applicant}"
..works under the covers as below:
WorkOrderCurrent workOrderCurrent = mbWorkOrderController.getWorkOrderCurrent();
workOrderCurrent.setApplicant(applicant);
mbWorkOrderController.setWorkOrderCurrent(workOrderCurrent);
This isn't true. It works under the covers as below:
mbWorkOrderController.getWorkOrderCurrent().setApplicant(applicant);
The correct <c:set> syntax is therefore as below:
<c:set value="#{mbUserController.userLoggedIn.username}"
target="#{mbWorkOrderController.workOrderCurrent}"
property="applicant" />
That said, all of this isn't the correct solution to the concrete problem you actually tried to solve. You should perform model prepopulating in the model itself. This can be achieved by using #ManagedProperty to reference another bean property and by using #PostConstruct to perform initialization based on it.
#ManagedBean(name = "mbWorkOrderController")
#SessionScoped
public class WorkOrderController {
#ManagedProperty("#{mbUserController.userLoggedIn}")
private User userLoggedIn;
#PostConstruct
public void init() {
workOrderCurrent.setApplicant(userLoggedIn.getUsername());
}
// ...
}
Perhaps you could explain the context a bit more, but here's another solution. If you're navigating from another page, you can pass some identifier of work WorkOrder in the URL, like this http://host:port/context/page.xhtml?workOrderId=1.
Then, you can set the identifier in the managed bean like this:
<h:html>
<f:viewParam name="workOrderId" value="#{mbWorkOrderController.id}"/>
</h:html>
You'll have to add a new property to your bean:
public class WorkOrderController {
private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
// ...
}
And then, after the property has been set by JSF, you can find the work order in a lifecycle event:
<h:html>
<f:viewParam name="workOrderId" value="#{mbWorkOrderController.id}"/>
<f:event type="preRenderView" listener="#{mbWorkOrderController.findWorkOrder()}"/>
</h:html>
public class WorkOrderController {
private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public void findWorkOrder() {
this.workOrderCurrent = null /* some way of finding the work order */
}
// ...
}
This strategy has the advantage of letting you have bookmarkable URLs.
I have a question regarding the lifecycle of session scoped CDI beans.
As far as I understand, a session scoped CDI bean is constructed by the container when the session starts and destroyed when the session ends. Before the bean is destroyed the #PreDestroy Method is invoked as described here https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html. It also says to release resources in this method.
In a JSF application I build I experience Memory Leak because the bean doesn't seem to be destroyed and hence the #PreDestroy Method is not invoked to free some references for the garbage collector. So I built a simple Application to test the behavior. My experience is that the session bean doesn't get destroyed when the session is over and furthermore it doesn't even get destroyed when the memory space is needed. I cannot believe I am the first to encounter this, but I don't find any information about this behavior..
So my question is: Shouldn't a CDI bean be destroyed - and hence the #PreDestroy Method be invoked - immediately after its context expired? And if not shouldn't it be at least destroyed when the space is needed?
My test Application:
I am not allowed to post a picture, but the outline is the very basic jsf webapp generated by eclipse. I also have the beans.xml file.
Test.java:
package com.test;
import java.io.Serializable;
import java.util.ArrayList;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
#SessionScoped
#Named
public class Test implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String test;
private ArrayList<ComplexType> cps;
private ArrayList<ComplexType> cps_2;
#PostConstruct
public void init() {
System.out.println("test postconstruct..");
test = "Cdi Test";
}
#PreDestroy
public void cleanUp() {
cps = null;
cps_2 = null;
System.out.println("test cleanUp....");
}
public void data_1() {
cps = new ArrayList<ComplexType>();
for(int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps.add(cp);
System.out.println(i);
}
System.out.println("data_1");
}
public void free_1() {
cps = null;
System.out.println("free_1");
}
public void data_2() {
cps_2 = new ArrayList<ComplexType>();
for(int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps_2.add(cp);
System.out.println(i);
}
System.out.println("data_1");
}
public void free_2() {
cps_2 = null;
System.out.println("free_1");
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
ComplexType.java:
package com.test;
public class ComplexType {
private int id;
private String[] name;
public ComplexType(int id, String[] name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String[] getName() {
return name;
}
public void setName(String[] name) {
this.name = name;
}
}
index.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<h:head>
<title>Cdi test </title>
</h:head>
<h:body>
<h:outputText value="#{test.test}"></h:outputText>
<h:form>
<h:commandButton value="cp_1 data" actionListener="#{test.data_1}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_1 Free" actionListener="#{test.free_1}">
<f:ajax></f:ajax>
</h:commandButton>
<br></br>
<h:commandButton value="cp_2 data" actionListener="#{test.data_2}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_2 Free" actionListener="#{test.free_2}">
<f:ajax></f:ajax>
</h:commandButton>
</h:form>
</h:body>
</html>
I open the index.xhtml page and the #PostConstruct Method gets invoked as expected. The heap space is exceeded when I invoke data_1 and data_2 both without freeing in between. When I free one of the resources in between or I invoke one method twice in a row the heap space is enough, as the garbage collector frees the memory. This works as I would expect it to work.
But when I invoke one data function, close the browser and hence the session, open a new browser and invoke one of the data functions again, then the application stops working as (I guess) the memory space is exceeded. The point is: the first session bean doesn't get destroyed and its #PreDestroy Method not invoked and therefore the ArrayList is still in the memory.
Can someone please explain to me what is going on here? Shouldn't a CDI bean be destroyed by the container as soon its context expires so that references can be set to null and the garbage collector can free resources?
I am using JBoss AS 7.1.1 and its default implementation JSF Mojarra 2.1.
Session beans (regardless CDI or JSF managed) stay alive until some session timeout exceeds (usually 30 minutes by default, dependent on application server), which you can specify in web.xml. Just closing the browser doesn't invalidate session and it wait to be destroyed by servlet container after timeout expiration. So, my assumption, such behaviour is just fine, #PreDestroy method will be invoked later.
The answer of #olexd basically explains what I was getting wrong in my mind, thank you very much! But invalidating the session after a determined period is not an option, so I had to use the comment of #geert3 as well, thank you for that! I am answering my own question to show how I have solved my particular problem in detail here.
What I was wrong about: I thought the session expires as soon as the browser is closed. This is wrong and it makes sense. One may want to close the browser and open it again to work in the same session as before.
For me this behaviour is not appropriate because I want to release resources as soon as the browser gets closed. So the answer is to manually invalidate the session like this:
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
As soon as this method is called, the #PreDestroy Method is called, exactly as I want it. Now I had to determine when to call this function. I searched for a way to listen to something like a browserclose event. There are the onbeforeunload and onunload events. onunload doesn't seem to work for me in Chrome, but the onbeforeunload does. See also this answer:
https://stackoverflow.com/a/16677225/1566562
So I wrote a hidden button that gets clicked by javascript on beforeunload and invokes an appropriate backingbean method. This works as I would expect it to work. I tested it on Chrome 43.0.2357.65 and IE 11, for now I am content with it. However it doesn't work with onunload, but this is not of concern for me right now.
So my final code likes this:
index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Cdi test</title>
<h:outputScript library="default" name="js/jquery-1.11.3.min.js"
target="head"></h:outputScript>
</h:head>
<h:body>
<h:outputText value="#{test.test}"></h:outputText>
<h:form id="overall">
<h:commandButton value="cp_1 data" actionListener="#{test.data_1}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_1 Free" actionListener="#{test.free_1}">
<f:ajax></f:ajax>
</h:commandButton>
<br></br>
<h:commandButton value="cp_2 data" actionListener="#{test.data_2}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_2 Free" actionListener="#{test.free_2}">
<f:ajax></f:ajax>
</h:commandButton>
<br></br>
<h:commandButton id="b" style="display:none"
actionListener="#{test.invalidate}"></h:commandButton>
</h:form>
<script type="text/javascript">
$(window).on('beforeunload', function() {
$('#overall\\:b').click();
});
</script>
</h:body>
</html>
Test.java
package com.test;
import java.io.Serializable;
import java.util.ArrayList;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;
#SessionScoped
#Named
public class Test implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String test;
private ArrayList<ComplexType> cps;
private ArrayList<ComplexType> cps_2;
#PostConstruct
public void init() {
System.out.println("test postconstruct..");
test = "Cdi Test";
}
#PreDestroy
public void cleanUp() {
cps = null;
cps_2 = null;
System.out.println("test cleanUp....");
}
public void data_1() {
cps = new ArrayList<ComplexType>();
for (int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps.add(cp);
System.out.println(i);
}
System.out.println("data_1");
}
public void free_1() {
cps = null;
System.out.println("free_1");
}
public void data_2() {
cps_2 = new ArrayList<ComplexType>();
for (int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps_2.add(cp);
System.out.println(i);
}
System.out.println("data_2");
}
public void free_2() {
cps_2 = null;
System.out.println("free_2");
}
public void invalidate() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
System.out.println("invalidate");
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
Note that I have used JQuery. This works with JBoss AS 7.1.1 and the default Weld implementation as well.
One thing to add: one doesn't have to set all the referenes manually to null. This makes sense as well, as it would be tedious..
I wanted to use #PostConstruct to initialize a bean in my webapp but I can't get it to work.
I've recreated the problem in a new project and it still won't work.
Am I missing something obvious here? As far as I can tell my init() method fulfills all the requirements listed in #PostConstruct API reference.
MyBean.java:
#ManagedBean
#RequestScoped
public class MyBean {
#ManagedProperty(value="15")
private int number = 10;
#PostConstruct
public void init(){
number = 20;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
number.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Simple JSF Facelets page</title>
</h:head>
<h:body>
Number = #{myBean.number}
</h:body>
</html>
I would expect Number = 20 but I get Number = 15.
#PostConstruct seems to be called before the injection with #ManagedProperty, assuming you have MyFaces 2.0, as they say here.
Make sure you are using Mojarra 2.1 because it should work.
You might try to debug to know if your init() method is called before the injection, or never called.
By default, Spring will not aware of the #PostConstruct and #PreDestroy annotation. To enable it, you have to either register CommonAnnotationBeanPostProcessor or specify the <context:annotation-config /> in bean configuration file.
I have the following 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:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<ui:composition template="./templates/fireLeftMenuTemplate.xhtml">
<ui:define name="left">
<h:form>
<p:menu model="#{gradingBean.courseMenu}"/>
</h:form>
</ui:define>
<ui:define name="content">
<h:form>
<p:accordionPanel binding="#{gradingBean.assignmentView}"/>
</h:form>
</ui:define>
</ui:composition>
</body>
The GradingBean:
#Named("gradingBean")
#ViewScoped
public class GradingBean {
#EJB
private AssignmentManager assignmentManager;
/*
* The assignmentMenu, listing all assignments for each course currently
* assisted by this grader
*/
private final DefaultMenuModel courseView = new DefaultMenuModel();
private final AccordionPanel assignmentView = new AccordionPanel();
public GradingBean() {
FireLogger.logInfo("Created GradingBean for user {0}", FireUtil.getLoggedinUserEmail());
}
#PostConstruct
private void constructBean() {
constructAssignmentView();
constructCourseMenu();
FireLogger.logInfo("Constructed bean");
}
private void constructAssignmentView() {
Tab tab = new Tab();
tab.setTitle("Hello");
assignmentView.getChildren().add(tab);
assignmentView.setRendered(true);
FireLogger.logInfo("Constructed assignmentView");
}
private void constructCourseMenu() {
/*
* For now we default to just one course at a time, since we have not
* implemented support for multiple courses as of yet.
*/
Submenu defaultCourse = new Submenu();
defaultCourse.setLabel("Objekt Orienterad Programmering IT");
/*
* add each assignment associated with this course
*/
ExpressionFactory expressionFactory =
FacesContext.getCurrentInstance()
.getApplication()
.getExpressionFactory();
for (Assignment assignment : assignmentManager.getAll()) {
MenuItem menuItem = new MenuItem();
menuItem.setValue(assignment.getTitle());
MethodExpression expression = expressionFactory.createMethodExpression(
FacesContext.getCurrentInstance().getELContext(), "#{gradingBean.printstuff('yay!')}", String.class, new Class[0]);
menuItem.setActionExpression(expression);
defaultCourse.getChildren().add(menuItem);
}
courseView.addSubmenu(defaultCourse);
FireLogger.logInfo("Constructed courseMenu");
}
public String printstuff(String stuff) {
FireLogger.logInfo("Printing! " + stuff);
return "hej";
}
public DefaultMenuModel getCourseMenu() {
return courseView;
}
public AssignmentManager getAssignmentManager() {
return assignmentManager;
}
public DefaultMenuModel getCourseView() {
return courseView;
}
public AccordionPanel getAssignmentView() {
return assignmentView;
}
public void setAssignmentManager(AssignmentManager assignmentManager) {
this.assignmentManager = assignmentManager;
}
/**
* Custom menuitem for the purpose of storing associated assignments and
* information related to them.
*/
private class AssignmentMenuItem extends MenuItem {
private static final long serialVersionUID = 1L;
private Assignment assignment;
public AssignmentMenuItem(Assignment assignment) {
super();
this.assignment = assignment;
setValue(assignment.getTitle());
}
/**
* Convenience method
*
* #param component
*/
public void addChild(UIComponent component) {
getChildren().add(component);
}
}
}
Please do not mind the code quality, it is for debugging.
The problem is this: whenever I enable the accordionPanel tag on the xhtml page, all other beans associated with this page stop working: for example, clicking any of the menuitems on the courseView menu (which DOES work perfectly in the absence of the accordion), does nothing but to reload the page. The same goes for ALL other bean action bindings (including those generated in the header).
What am I missing here? As soon as I remove the accordionPanel tag, as mentioned, it works just fine. I am guessing it has something to do with the request cycle, but I am at a loss as to just what is going wrong.
EDIT:
Logging output for pressing the menuitems (which one does not matter) 2 times:
INFO: se.gu.fire.backend.GradingBean: Created GradingBean for user a#a.com
INFO: se.gu.fire.backend.GradingBean: Constructed assignmentView
INFO: se.gu.fire.backend.GradingBean: Constructed courseMenu
INFO: se.gu.fire.backend.GradingBean: Constructed bean
INFO: se.gu.fire.backend.GradingBean: Created GradingBean for user a#a.com
INFO: se.gu.fire.backend.GradingBean: Constructed assignmentView
INFO: se.gu.fire.backend.GradingBean: Constructed courseMenu
INFO: se.gu.fire.backend.GradingBean: Constructed bean
Notice how the cycle seems to get reset, and the bean reloaded whenever this happens...this happens for all other bindings to other beans on this page as well.
The code posted so far does not indicate that (using #Named #ViewScoped makes no sense), but the symptoms are recognizeable in case of using the binding attribute on a property of a fullworthy JSF view scoped managed bean (with #ManagedBean #ViewScoped).
The binding attribute is (like as id attribute and all taghandlers) evaluated during the view build time. The view is by default (with partial state saving enabled) built on every HTTP request. When the view is built, then the view scope is ready for use. View scoped managed beans are stored in there.
However, the view scope is by default not available during view build time. This means that any EL expression referencing a view scoped bean which is evaluated during building the view will create a brand new and completely separate instance of the view scoped bean, with all properties set to default. This is then reused instead.
This chicken-egg issue can be solved by using the binding attribute exclusively on request scoped beans, or to turn off partial state saving by setting the web.xml context parameter javax.faces.PARTIAL_STATE_SAVING to false. This has been reported as JSF issue 1492 and is fixed in the upcoming JSF 2.2.
See also:
#PostConstruct method is called even if the ManagedBean has already been instantiated (e.g. on AJAX-calls)
JSTL in JSF2 Facelets... makes sense?