I'm dynamically injecting some JS into all my pages, and this works fine in Mojarra, but I've found out it fails in myfaces.
My event listener is configured as:
<application>
<system-event-listener>
<system-event-listener-class>a.b.HeadResourceListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
<source-class>javax.faces.component.UIOutput</source-class>
</system-event-listener>
</application>
With code looking something like:
public class HeadResourceListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return "javax.faces.Head".equals(((UIComponent) source).getRendererType());
}
#Override
public void processEvent(SystemEvent event) {
UIComponent outputScript = new UIOutput();
outputScript.setRendererType("javax.faces.resource.Script");
UIOutput content = new UIOutput();
content.setValue("var abc='';");
outputScript.getChildren().add(content);
context.getViewRoot().addComponentResource(context, outputScript, "head");
}
}
Unfortunately, with myfaces, the rendererType of the source is never javax.faces.Head (I only found occurrences of javax.faces.resources.Script and javax.faces.resources.Stylesheet)
Is there any specific reason why the behaviour differs here?
Any suggestions for another solution maybe?
EDIT
As suggested, when linking this listener to source-class , it is triggered in myfaces. However, on postback, I get duplicate id errors...
Caused by: org.apache.myfaces.view.facelets.compiler.DuplicateIdException: Component with duplicate id "j_id__v_7" found. The first component is {Component- Path : [Class: javax.faces.component.UIViewRoot,ViewId: /user/login.xhtml][Class: org.apache.myfaces.component.ComponentResourceContainer,Id: javax_faces_location_head][Class: javax.faces.component.UIOutput,Id: j_id__v_7]}
at org.apache.myfaces.view.facelets.compiler.CheckDuplicateIdFaceletUtils.createAndQueueException(CheckDuplicateIdFaceletUtils.java:148)
at [internal classes]
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:73)
at org.apache.myfaces.tomahawk.application.ResourceViewHandlerWrapper.renderView(ResourceViewHandlerWrapper.java:169)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:73)
It's a bug in MyFaces.
JSF 2.3 specification says the following in table 9.2:
TABLE 9-2 Standard HTML RenderKit Tag Library
getComponentType() getRendererType()
javax.faces.Output javax.faces.Head
As per chapter 4.1.10.1 of the same specification, javax.faces.Output maps to javax.faces.component.UIOutput.
4.1.10.1 Component Type
The standard component type for UIOutput components is “javax.faces.Output”.
So, the <h:head> must be an instance of UIOutput.
If we look back at table 9.2, the javax.faces.Output can have multiple renderers, so you can indeed only listen on <source-class> of javax.faces.component.UIOutput and you'd have to manually inspect its renderer type to be javax.faces.Head. Your HeadResourceListener is correct.
See also:
How to load a JavaScript file on all pages programmatically
Related
I have a web application that uses optional modules. The modules are implemented as Web Fragment projects, their jars may or may not be deployed with the war depending on the build profile.
A module can contain it's own module.taglib.xml with a http://company.com/module namespace and some tags.
The war xhtml templates use module tags like this:
<ui:composition ... xmlns:mod="http://company.com/module">
<c:if test="#{moduleDeployed}">
<mod:someTag />
</c:if>
Problems.
When the module is not deployed, the war pages work fine, but in ProjectStage.Development I get FacesMessage warnings:
Warning: This page calls for XML namespace
http://company.com/module declared with prefix mod but no
taglibrary exists for that namespace.
As far as I can see, JSF specification doesn't define what happens, when a template uses a nonexistent tag library. So with my current approach war pages can stop working after an upgrade or a switch to a different JSF implementation.
Questions.
Is there a (not very ugly) way to disable this specific warning?
Is there a better approach to using optional facelet tag libraries?
As of now I plan to disable the warning anyway I can: e.g. override Messages renderer and check message string if I have to. If the problem 2 manifests, make the build supply placeholder taglib.xml files for not deployed modules.
Even though placeholder taglibs seemed like a pretty good solution, they also seemed harder to implement and maintain.
So in the end I went with filtering the messages. This is likely Mojarra specific: the message text, the fact that the iterator allows removal (this isn't forbidden by the spec, but it's not required either). It's known to work with Mojarra 2.2.8 to 2.2.13.
public class SuppressNoTaglibraryExistsFacesMessage implements SystemEventListener {
private static final Pattern PTTRN_NO_TAGLIBRARY_EXISTS_FOR_NAMESPACE =
Pattern.compile("Warning: This page calls for XML namespace \\S+ declared with "
+ "prefix \\S+ but no taglibrary exists for that namespace.");
#Override
public void processEvent(SystemEvent event) {
Iterator<FacesMessage> messages = FacesContext.getCurrentInstance().getMessages();
while (messages.hasNext()) {
String messageSummary = messages.next().getSummary();
if (PTTRN_NO_TAGLIBRARY_EXISTS_FOR_NAMESPACE.matcher(messageSummary).matches()) {
messages.remove();
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return true;
}
}
Bind the listener only in Development project stage.
public class SubscribeListenersAfterApplicationPostConstructListener
implements SystemEventListener {
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
Application application = (Application) event.getSource();
if (ProjectStage.Development.equals(application.getProjectStage())) {
application.subscribeToEvent(PostAddToViewEvent.class, UIViewRoot.class,
new SuppressNoTaglibraryExistsFacesMessage());
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
And in faces-config.xml:
<system-event-listener>
<system-event-listener-class><packages>.SubscribeListenersAfterApplicationPostConstructListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostConstructApplicationEvent</system-event-class>
</system-event-listener>
I'm using a ViewHandler to block all input elements on any accessed page, if certain criteria is met.
This works great for the input elements in the 'primary' xhtml files, but the input elements within composite components aren't being blocked. I figured it has to do with the fact that JSF embeds these components only after my ViewHandler has finished it's job.
Does anyone have an idea of how I can disable the elements in the composite as well?
A ViewHandler is the wrong tool for the job. It's intented to create, build and restore views and to generate URLs for usage in JSF forms and links. It's not intented to manipulate components in a view.
For your particular functional requirement, a SystemEventListener on PostAddToViewEvent is likely the best suit. I just did a quick test, it works for me on inputs in composites as well.
public class MyPostAddtoViewEventListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return (source instanceof UIInput);
}
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
UIInput input = (UIInput) event.getSource();
if (true) { // Do your check here.
input.getAttributes().put("disabled", true);
}
}
}
To get it to run, register it as follows inside <application> of faces-config.xml:
<system-event-listener>
<system-event-listener-class>com.example.MyPostAddtoViewEventListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
</system-event-listener>
In my app, user should be able to switch the locale (the language used to render text on pages). Tons of tutorials are using FacesContext.getCurrentInstance().getViewRoot().setLocale(). For example: http://www.mkyong.com/jsf2/jsf-2-internationalization-example/. But, that simply doesn't work in JSF 2.0 (it did work in 1.2). The language never switches. No errors or anything. The same code worked fine in JSF 1.2.
What is the correct and definitive approach? I have cobbled together a solution, but not sure if this is the correct one. This works fine. The language switches after user clicks on English or French. Here is code snippet to give you some idea.
#ManagedBean(name = "switcher")
#SessionScoped
public class LanguageSwitcher {
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
public String switchLocale(String lang) {
locale = new Locale(lang);
return FacesContext.getCurrentInstance().getViewRoot().getViewId() +
"?faces-redirect=true";
}
//getLocale() etc. omitted for brevity
}
The XHTML:
<f:view locale="#{switcher.locale}">
<h:outputText value="#{msg.greeting}" />
<h:commandLink value="English" action="#{switcher.switchLocale('en')}" />
<h:commandLink value="French" action="#{switcher.switchLocale('fr')}" />
</f:view>
Just to give you more info, here is the config file.
<application>
<locale-config>
<supported-locale>en</supported-locale>
<supported-locale>fr</supported-locale>
</locale-config>
<resource-bundle>
<base-name>com.resources.Messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
Once again, this works. But, I haven't changed the locale of JSF itself by calling any API in any way. This gives me somewhat of a creepy feeling. Is this the correct way to change user's locale?
OK, at the risk of answering my own question, I will like to summarize all the different approaches that I have found.
The basic approach is what I am already doing. That is, have a managed bean in session scope that returns the Locale of the user. This locale needs to be used in every XHTML using <f:view locale="...">. I learned this technique from a post by BalusC, so thanks are due there.
Now, the concern is the use of the f:view element. This needs to be repeated in every page, a potential source of defect if omitted by mistake. I have found a couple of ways of solving this problem.
Approach #1: Create a Facelet template and add the f:view element there. Individual template user pages don't have to worry about adding this element.
Approach #2 uses a phase listener. #meriton has posted the solution here. Thank you for that.
Approach #3 uses a custom view handler that extends MultiViewHandler and returns user's locale from the calculateLocale() method. This is described in the book Beginning JSF 2 APIs and JBoss Seam By: Kent Ka Iok Tong. Here is a slightly altered example from the book:
public class MyViewHandler extends MultiViewHandler {
public Locale calculateLocale(FacesContext context) {
HttpSession session = (HttpSession) context.getExternalContext()
.getSession(false);
if (session != null) {
//Return the locale saved by the managed bean earlier
Locale locale = (Locale) session.getAttribute("locale");
if (locale != null) {
return locale;
}
}
return super.calculateLocale(context);
}
}
Then register it in faces config.
<application>
<view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>
This is somewhat more elegant than the phase listener. Unfortunately, MultiViewHandler is an internal non-API class from the com.sun.faces.application.view package. That incurs some risk going forward.
With either approach #2 or #3, there is no need for the f:view element in the pages.
One can use custom view handler that extends javax.faces.application.ViewHandlerWrapper and returns user's locale from the calculateLocale() method.
This is definitely better than extending MultiViewHandler from the proprietary SUN package com.sun.faces.application.view, no matter what is described in the book Beginning JSF 2 APIs mentioned in your suggestion. Apart from that, your original approach is absolutely OK:
public class MyViewHandler extends ViewHandlerWrapper {
public Locale calculateLocale(FacesContext context) {
HttpSession session = (HttpSession) context.getExternalContext()
.getSession(false);
if (session != null) {
//Return the locale saved by the managed bean earlier
Locale locale = (Locale) session.getAttribute("locale");
if (locale != null) {
return locale;
}
}
return super.calculateLocale(context);
}
}
Then register it in faces config.
<application>
<view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>
I had a related problem recently. In my case, the JSF implementation forgot the view locale set by UIViewRoot.setLocale() after navigating to a different view. I rather consider this a bug in the JSF impl, but I didn't have time to make sure.
I didn't particularly like the <f:view> approach, as that tag has been obsoleted by facelets - except for keeping the locale, it seems. This made my leary of including it in a Facelets template. I therefore wrote the following PhaseListener:
/**
* PhaseListener that keeps the current view locale in the session while no request is being processed, to work around
* bugs where JSF forgets the changed locale.
*/
public class SaveViewLocaleToSessionPhaseListener implements PhaseListener {
private static final String key = "locale";
#Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
#Override
public void beforePhase(PhaseEvent event) {
// do nothing
}
#Override
public void afterPhase(PhaseEvent event) {
PhaseId currentPhase = event.getPhaseId();
if (currentPhase == PhaseId.RESTORE_VIEW) {
viewRoot().setLocale((Locale) sessionMap().get(key));
} else if (currentPhase == PhaseId.RENDER_RESPONSE) {
sessionMap().put(key, viewRoot().getLocale());
}
}
private Map<String, Object> sessionMap() {
return FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
}
private UIViewRoot viewRoot() {
return FacesContext.getCurrentInstance().getViewRoot();
}
}
However, I can not offer any solid evidence that this is really better than simply using <f:view>.
But, I haven't changed the locale of JSF itself in any way.
Sure you did: The <f:view> tag reads the locale from the value expression, and passes it to UIViewRoot.setLocale().
We need to call a action method while invoking the first page of the application. For example, the first page is index.jsp, when we are directly calling this page the action method is not called. To achieve that, we have written another page where it uses java script to click on the button and call the action method, that navigates to the index.jsp.
I feel that there should be proper way in JSF to achieve this task. What is the bet way to do that? I have told the team that we can call the action method in the constructor while loading the page. Is it the correct way? What are the possible solutions?
Just do the job in #PostConstruct method of an application scoped bean which is is eagerly constructed or which is at least bound to the page.
#ManagedBean(eager=true)
#ApplicationScoped
public class Bean {
#PostConstruct
public void init() {
// Here.
}
}
Alternatively, if JSF (read: the FacesContext) has no relevant role in the actual job, you can also use a ServletContextListener.
#WebListener
public class Config implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
// Do stuff during webapp startup.
}
public void contextDestroyed(ServletContextEvent event) {
// Do stuff during webapp shutdown.
}
}
If you're not on Servlet 3.0 yet, register it in web.xml as follows.
<listener>
<listener-class>com.example.Config</listener-class>
</listener>
See also:
Using special auto start servlet to initialize on startup and share application data
Using JSF 2.0
If you want to take some action when your application starts (even if is not yet accesed ), you can use a SystemEventListener and subscribe it to PostConstructApplicationEvent.
Example of the listener:
package listeners;
import javax.faces.application.Application;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ListenerFor;
import javax.faces.event.PostConstructApplicationEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
public class MySystemListener implements SystemEventListener{
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
System.out.println("started");
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
To suscribe you have to include this fragment in the faces-config.xml
<application>
<system-event-listener>
<system-event-listener-class>
listeners.MySystemListener
</system-event-listener-class>
<system-event-class>
javax.faces.event.PostConstructApplicationEvent
</system-event-class>
</system-event-listener>
</application>
And if you want to take the action when the user enters to a specific page, you could use another system event and f:event tag to receive a notification before the page is displayed.
For example:
...
<h:body>
<f:event type="preRenderView" listener="#{bean.action}"/>
<h:form>
<!--components-->
</h:form>
</h:body>
...
Here are more details on using system events: http://andyschwartz.wordpress.com/2009/07/31/whats-new-in-jsf-2/#system-events.
In JSF 1.2, one way I think you could receive a notification will be with PhaseListener's and check the id of the view currently rendering.
I know of two ways of creating custom JSF components:
1. Native JSF way: creating JSF component class, tag, etc.
2. Facelets way: defining component in a xhtml file and then creating appropriate decrption in facelets taglib.
Currently I work on a project in which introducing facelets is unfortunately out of the question. On the other hand, creating custom components the standard JSF way seems like a pain in the ass.
Is there maybe a third party library that allows creating custom components in the way similar to facelets but doesn't entail the need of using non-standard renderer?
You can do a limited amount of templating using (for example) jsp:include and f:subview.
Alternatively, you can extend a UIComponent overriding selected methods and then provide it via an existing tag and a managed bean using the binding attribute. This still requires a reasonably detailed understanding of component development (and the consequences of this choice), but could cut down the number of files/volume of code significantly.
This approach is a bit of a hack, but might be OK for short-term stuff. You wouldn't do it for component libraries you want to distribute or components requiring long term maintenance.
The new component:
public class QuickComponent extends HtmlOutputText {
#Override public void encodeAll(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.writeText("I'm not really a HtmlOutputText", null);
for (UIComponent kid : getChildren()) {
if (kid instanceof UIParameter) {
UIParameter param = (UIParameter) kid;
writer.startElement("br", this);
writer.endElement("br");
writer.writeText(param.getName() + "=" + param.getValue(), null);
}
}
}
}
The bean providing an instance:
/**Request-scope managed bean defined in faces-config.xml*/
public class QuickComponentProviderBean {
private QuickComponent quick;
public void setQuick(QuickComponent quick) {
this.quick = quick;
}
public QuickComponent getQuick() {
if (quick == null) {
quick = new QuickComponent();
}
return quick;
}
}
Note: don't reuse a single bean property for multiple tags in your views, or they'll reference the same object instance.
Adding the new component to the view:
<h:outputText binding="#{quickComponentProviderBean.quick}">
<f:param name="Hello" value="World" />
</h:outputText>
Note: the attributes that can be defined have not changed. They're fixed by the TLD.