Exclude resources in JSF - jsf

I'm using Primefaces. For some pages I want to exclude the resources which are included by primefacs, especially the theme resource.
<link type="text/css" rel="stylesheet" href="/context/javax.faces.resource/theme.css?ln=primefaces-owntheme" />
I try that with a SystemEventListener as following:
public class PrimeFacesResourceRemover implements javax.faces.event.SystemEventListener {
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = context.getViewRoot();
for (UIComponent resource : viewRoot.getComponentResources(context, "head")) {
Map<String, Object> attrs = resource.getAttributes();
String resourceLibrary = (String) attrs.get("library");
String resourceName = (String) attrs.get("name");
if ("primefaces-owntheme".equals(resourceLibrary) && "theme.css".equals(resourceName)) {
// Remove resource from view
context.getViewRoot().removeComponentResource(context, resource, HEAD);
}
}
}
/**
* {#inheritDoc}
*/
#Override
public boolean isListenerForSource(Object source) {
return (source instanceof UIViewRoot);
}
}
and in the faces-config.xml
<application>
<system-event-listener>
<system-event-listener-class>
com.mycompany.mavenproject1.PrimeFacesResourceRemover
</system-event-listener-class>
<system-event-class>
javax.faces.event.PostAddToViewEvent
</system-event-class>
</system-event-listener>
</application>
This works well, when I include a resouce on a page manually, but those not work with the resouces which are included by Primefaces. How can I remove those resouces?

The theme.css is not been added as a dynamic component resource, but it's been hardcoded in PrimeFaces' HeadRenderer. The OmniFaces CombinedResourceHandler was also struggling with this.
The solution is rather simple: override it with a custom HeadRenderer (without any #ResourceDependency on theme.css, of course!):
public class HeadRenderer extends Renderer {
#Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
context.getResponseWriter().startElement("head", component);
}
#Override
public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
// NOOP.
}
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
for (UIComponent resource : context.getViewRoot().getComponentResources(context, "head")) {
resource.encodeAll(context);
}
context.getResponseWriter().endElement("head");
}
}
Register it as follows in webapp's faces-config.xml:
<render-kit>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>javax.faces.Head</renderer-type>
<renderer-class>com.example.HeadRenderer</renderer-class>
</renderer>
</render-kit>

For all of you who would like to remove theme.css on all pages (that's the first thing I am doing), and still don't know this trick.
You'll need to drop these few lines in your web.xml:
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>none</param-value>
</context-param>

Related

Override PrimeFaces' MediaRenderer

Im trying to override Primeface's 7.0 MediaRenderer, I created my own MyMediaRenderer with Primeface's 8.0 method:
public class MyMediaRenderer extends org.primefaces.component.media.MediaRenderer {
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
...
if (value instanceof StreamedContent && PDFPlayer.MIME_TYPE.equals(player.getType())) {
...
}
...
}
}
I also added to the end of src/main/webapp/WEB-INF/faces-config.xml :
<render-kit>
<renderer>
<component-family>org.primefaces.component.media</component-family>
<renderer-type>org.primefaces.component.media.MediaRenderer</renderer-type>
<renderer-class>redacted.MyMediaRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
What am I missing here? The method is still called from org.primefaces.component.media.MediaRenderer
Your renderkit is incorrect use the following:
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.MediaRenderer</renderer-type>
<renderer-class>redacted.MyMediaRenderer</renderer-class>
</renderer>

Disable components in a large project

with a lot of developers and plenty of juniors I want to disable certain components such as <p:spacer> to prohibit using components for html/css issues. I want to limit the available components for libraries like omnifaces / primefaces / richfaces to a whitelist / blacklist thing basically.
Would this be a reasonable feature request for a library like omnifaces or is it to hard to build / to localized?
Basically, you can achieve this by providing a custom Application implementation (based on ApplicationWrapper) wherein you override the desired createComponent() method and throw e.g. IllegalArgumentException when a blacklisted component type and/or renderer type is passed.
Here's a kickoff example:
public class YourApplication extends ApplicationWrapper {
private static final Set<String> BLACKLISTED_COMPONENT_TYPES = unmodifiableSet(new HashSet<>(asList(
"org.primefaces.component.Spacer",
"com.example.SomeComponentType",
"com.example.OtherComponentType"
// Etc..
)));
private final Application wrapped;
public YourApplication(Application wrapped) {
this.wrapped = wrapped;
}
#Override
public UIComponent createComponent(FacesContext context, String componentType, String rendererType) {
if (BLACKLISTED_COMPONENT_TYPES.contains(componentType)) {
throw new IllegalArgumentException("You are not allowed to use this component.");
}
return super.createComponent(context, componentType, rendererType);
}
#Override
public Application getWrapped() {
return wrapped;
}
}
You can get it to run with this factory:
public class YourApplicationFactory extends ApplicationFactory {
private final ApplicationFactory wrapped;
public YourApplicationFactory(ApplicationFactory wrapped) {
this.wrapped = wrapped;
}
#Override
public Application getApplication() {
return new YourApplication(wrapped.getApplication());
}
#Override
public void setApplication(Application application) {
wrapped.setApplication(application);
}
}
Which is registered in faces-config.xml as below:
<factory>
<application-factory>com.example.YourApplicationFactory</application-factory>
</factory>
You can use tag file feature of jsf. You will declare tag file for each component that you want to use. After that, your team will only use these tag file in your project.

Can't keep faces message after navigation from preRender

in my preRender code for a page i add faces message then make navigation to another page as follows:
if(error){
addMessageToComponent(null,"AN ERROR HAS OCCURRED");
FacesContext.getCurrentInstance().getExternalContext().getFlash()
.setKeepMessages(true);
navigateActionListener("myoutcome");
}
and the util methods for adding message and navigation are:
public static String getClientId(String componentId)
{
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot root = context.getViewRoot();
UIComponent c = findComponent(root, componentId);
return c.getClientId(context);
}
public static UIComponent findComponent(UIComponent c, String id)
{
if (id.equals(c.getId())) { return c; }
Iterator<UIComponent> kids = c.getFacetsAndChildren();
while (kids.hasNext())
{
UIComponent found = findComponent(kids.next(), id);
if (found != null) { return found; }
}
return null;
}
/**
* #param componentId
* : the id for the jsf/primefaces component without formId:
* prefix. <br>
* if you use null then the message will be added to the
* h:messages component.
**/
public static void addMessageToComponent(String componentId, String message)
{
if (componentId != null)
componentId = GeneralUtils.getClientId(componentId);
FacesContext.getCurrentInstance().addMessage(componentId,
new FacesMessage(message));
}
public static void navigateActionListener(String outcome)
{
FacesContext context = FacesContext.getCurrentInstance();
NavigationHandler navigator = context.getApplication()
.getNavigationHandler();
navigator.handleNavigation(context, null, outcome);
}
but messages are not saved and so it doesn't appear after redirect.
please advise how to fix that.
The preRenderView event runs in the very beginning of the RENDER_RESPONSE phase. It's too late to instruct the Flash scope to keep the messages. You can do this at the latest during the INVOKE_APPLICATION phase.
Since there's no standard JSF component system event for this, you'd need to homebrew one:
#NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
To publish this, you need a PhaseListener:
public class PostInvokeActionListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
After registering it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
You'll be able to use the new event as follows:
<f:event type="postInvokeAction" listener="#{bean.init}" />
You only need to make sure that you've at least a <f:viewParam>, otherwise JSF won't enter the invoked phase at all.
The JSF utility library OmniFaces already supports this event and the preInvokeAction event out the box. See also the showcase page which also demonstrates setting a facesmessage for redirect.

JSF 2 equivalent of IBM's hx:scriptCollector pre- and postRender

I am migrating an old JSF application from WebSphere to JBoss: the old version uses an IBM implementation of JSF. My question concerns the following component:
<hx:scriptCollector id="aScriptCollector"
preRender="#{aBean.onPageLoadBegin}" postRender="#{aBean.onPageLoadEnd}">
To reproduce the preRender behavior in JSF 2 I use a binding for the form (s. here). My questions:
1) Do you know a trick for simulating postRender in JSF 2?
2) Do you think is the trick I am using for preRender "clean"?
Thanks a lot for your help!
Bye
Closest what you can get to achieve exactly the same hooks is
<f:view beforePhase="#{bean.beforePhase}" afterPhase="#{bean.afterPhase}">
with
public void beforePhase(PhaseEvent event) {
if (event.getPhaseId == PhaseId. RENDER_RESPONSE) {
// ...
}
}
public void afterPhase(PhaseEvent event) {
if (event.getPhaseId == PhaseId. RENDER_RESPONSE) {
// ...
}
}
The preRender can be achieved in an easier manner, put this anywhere in your view:
<f:event type="preRenderView" listener="#{bean.preRenderView}" />
with
public void preRenderView(ComponentSystemEvent event) {
// ...
}
(the argument is optional, you can omit it if never used)
There's no such thing as postRenderView, but you can easily create custom events. E.g.
#NamedEvent(shortName="postRenderView")
public class PostRenderViewEvent extends ComponentSystemEvent {
public PostRenderViewEvent(UIComponent component) {
super(component);
}
}
and
public class PostRenderViewListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostRenderViewEvent.class, context.getViewRoot());
}
}
which you register in faces-config.xml as
<lifecycle>
<phase-listener>com.example.PostRenderViewListener</phase-listener>
</lifecycle>
then you can finally use
<f:event type="postRenderView" listener="#{bean.postRenderView}" />
with
public void postRenderView(ComponentSystemEvent event) {
// ...
}

JSF 2.0 encodeAll() invoked prior to sessionCreated()

I have created a custom component in JSF 2.0. Its purpose is to display a session attribute value in a input text box. I want the session attribute to be created in HttpSessionListener sessionCreated method. The problem is encodeAll method is getting invoked prior to sessionCreated method. What should I do to make sessionCreated invoked prior to encodeAll?
web.xml
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/components.taglib.xml</param-value>
</context-param>
<listener>
<listener-class>mycomp.jsf.listener.MyListener</listener-class>
</listener>
components.taglib.xml
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
version="2.0">
<namespace>http://mycomp/jsf/components</namespace>
<tag>
<tag-name>mycomp</tag-name>
<component>
<component-type>MyComp</component-type>
</component>
</tag>
</facelet-taglib>
MyListener.java
public class MyListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
session.setAttribute("sessionid", session.getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
}
}
MyComp.java
#FacesComponent(value = "MyComp")
public class MyComp extends UIComponentBase {
public MyComp() {
setRendererType(null);
}
protected Class getComponentClass() {
return this.getClass();
}
#Override
public void decode(FacesContext context) {
//some logic
}
#Override
public void encodeAll(FacesContext context) throws IOException {
String clientId = getClientId(context) + ":token";
HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
String sessionId = (String) session.getAttribute("sessionid");
if (sessionId == null || "".equals(sessionId)) {
throw new RuntimeException("sessionid is missing!");
}
ResponseWriter writer = context.getResponseWriter();
writer.startElement("input", this);
writer.writeAttribute("type", "text", "type");
writer.writeAttribute("name", clientId, "name");
writer.writeAttribute("value", sessionId, "value");
writer.endElement("input");
}
#Override
public String getFamily() {
return null;
}
}
index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:t="http://mycomp/jsf/components">
<h:head>
</h:head>
<h:body>
<h:form>
<t:mycomp />
</h:form>
</h:body>
</html>
Replace getSession(false) by getSession(true).
HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
The boolean tells whether the session should be created or not when it isn't created yet. With getSession(false) you're thus risking getting a null when it isn't been created at that point yet and hence the NullPointerException whenever you call any methods on it. See also the javadoc.
Unrelated to the concrete problem, cleaner is to use ExternalContext#getSessionMap() instead. Having "raw" javax.servlet imports in your JSF code namely usually indicates smells in the JSF code. I.e. something is been done unnecessarily overcomplicated. You should then look for simpler ways with pure JSF API.
Replace
HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
String sessionId = (String) session.getAttribute("sessionid");
if (sessionId == null || "".equals(sessionId)) {
throw new RuntimeException("sessionid is missing!");
}
by
String sessionId = (String) context.getExternalContext().getSessionMap().get("sessionid");
if (sessionId == null || "".equals(sessionId)) {
throw new RuntimeException("sessionid is missing!");
}

Resources