Programmatically get expression value of facelets parameter (variable) - jsf

Following java code allows to access any object or variable from faces context:
ELContext elCtx = facesContext.getELContext();
ExpressionFactory exprFac = facesContext.getApplication().getExpressionFactory();
MyProperty myProperty = (MyProperty) exprFac.createValueExpression(elCtx, "#{somebean.someattr.someproperty}", MyProperty.class).getValue(elCtx);
I use the code from within my custom converter to read additional converting parameters from context.
The code works correctly if #{somebean} is defined as normal backing bean within JSF context.
Facelets allow to create 'shortcut' to JSF expressions. Example:
<ui:param name="shortcut" value="#{somebean.someattr.someproperty}" />
<div>#{somebean.someattr.someproperty} equals #{shortcut}</div>
In this case both #{somebean.someattr.someproperty} and #{shortcut} have the same value.
However these 'shortcut' names are not accessible using java code above. For example:
MyProperty myProperty1 = (MyProperty) exprFac.createValueExpression(elCtx, "#{somebean.someattr.someproperty}", MyProperty.class).getValue(elCtx);
// myProperty1 has expected value
MyProperty myProperty2 = (MyProperty) exprFac.createValueExpression(elCtx, "#{shortcut}", MyProperty.class).getValue(elCtx);
// myProperty2 is null
Is there a way to access a facelets context and to read 'shortcut' parameter values, defined on the current JSF page?

I had the same problem and have chosen the following approach:
/**
* Führt eine Expression im aktuellen Faces EL Context
* UND im Facelets El Context aus.
*
* #param facesContext
* #param expression
* #return object
*/
private static Object executeExpressionInUIContext (final FacesContext facesContext, final String expression) {
final ELContext elContext = facesContext.getELContext();
final Application application = facesContext.getApplication();
Object result = executeExpressionInElContext(application, elContext, expression);
if (null == result) {
FaceletContext faceletElContext = (FaceletContext) FacesContext.getCurrentInstance().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
result = executeExpressionInElContext(application, faceletElContext, expression);
}
return result;
}
private static Object executeExpressionInElContext (Application application, ELContext elContext, String expression) {
ExpressionFactory expressionFactory = application.getExpressionFactory();
ValueExpression exp = expressionFactory.createValueExpression(elContext, expression, Object.class);
return exp.getValue(elContext);
}
"ui:param" is part of the Facelet view handling technology. Facelets extends JSF.
Both technologies use their own Context when storing variables.
Beside the Faces El Context there is a Facelet El Context (FaceletContext).
The stated method evaluates expressions in both contexts. Be aware that this will not work if two values are stored under the same name in each context.

It seems that facelet shortcuts do not exist in the context, where I try to access them.
I have made following workaround: On JSF page where my input element is placed, I have added a <f:param> element as child of the input with my converter.
<h:inputText id="myid" value="#{action.myinput}">
<f:converter converterId="myConverter" />
<f:param name="converterParameters" shortcut="#{somebean.someattr.someproperty}"/>
</h:inputText>
Then in converter I'm able to find UIParam element as one of the input children and read my shortcuts from it.
public Object getAsObject(FacesContext context, UIComponent component, String value) {
MyProperty myProperty = null;
try {
for (final UIComponent child : component.getChildren()) {
if ("converterParameters".equals(child.getAttributes().get("name"))) {
final ELContext elCtx = context.getELContext();
myProperty = (MyProperty) child.getValueExpression("shortcut").getValue(elCtx);
break;
}
}
if (myProperty == null) {
throw new NullPointerException("My property is undefined.");
}
} catch (Exception e) {
LOG.error("Cannot convert " + value + ". Use <f:param name=\"converterParameters\" "
+ "shortcut=\"#{here.comes.shortcut}\"/> for your input element. ", e);
throw new ConverterException("Cannot initialize converter.", e);
}
//...
}

The mapping of ui:param is not stored in context, it's in the VariableMapper of each individual ValueExpression.
So if you need to create ValueExpression programmatically, relying on another ValueExpression's varMapper, you can do something like this:
VariableMapper varMapper = new DefaultVariableMapper();
varMapper.setVariable(mappingName, component.getValueExpression(mappedAttributeName));
return new ValueExpressionImpl(expression, null, null, varMapper, expectedType);

Related

Lambda containing Method is not called from JSF

I have a problem which occurs using Labda Expression and JSF 2.2
No matter which tag I use, as soon as the calling method contains a lamda expression, the method ist not called.
<p:commandButton action="#{bean.doSomthing}" />
public void doSomthing() {
// never called
// Lambda stuff happening
List<Customer> charlesWithMoreThan100Points = customers
.stream()
.filter(c -> c.getPoints() > 100 && c.getName().startsWith("Charles"))
.collect(Collectors.toList());
}
Without the Lambda Expression the Method is called as expected.
Debugging the actionListener has shown the this.value.getValue(elContext) does not trigger a call to the action method in the backing bean.
public void processAction(ActionEvent actionEvent) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
ELContext elContext = context.getELContext();
StreamedContent content = (StreamedContent)this.value.getValue(elContext);
// content is null. Value returning method is not called.

primefaces value expression on DefaultMenuItem

Hi i trying to set value expresion on some attribute (for example attribute class) of DefaultMenuItem of Primefaces. Something like:
<p:menuitem class="#{view.viewId.endsWith('index.xhtml') ? 'menuitm-hovered' : ''}" value="Home" url="/" />
exaclty these peace
class="#{view.viewId.endsWith('index.xhtml') ? 'menuitm-hovered' : ''}"
but programaticaly. On bean should be something like these:
menuItem.setValueExpression("class", Helper2.createValueExpression("#{view.viewId.endsWith('index.xhtml') ? 'menuitm-hovered' : ''}", Object.class));
DefaultMenuItem dosent have setValueExpression method so im stuck and I dont know how i could do that. If someone could help i will be grateful for help.
As far as i know setValueExpression can be done in GraphicImage (in these case with value attribute)
GraphicImage image = new GraphicImage();
image.setValueExpression("value", Helper2.createValueExpression("#{position.images.link}", Object.class));
Below static method of Helper2 class:
public static ValueExpression createValueExpression(String expression, Class clazz) {
FacesContext fc = FacesContext.getCurrentInstance();
ELContext elContext = fc.getELContext();
ExpressionFactory expFactory = fc.getApplication().getExpressionFactory();
ValueExpression ret = expFactory.createValueExpression(elContext, expression, clazz);
return ret;
}
Try this:
MenuButton menuButton = new MenuButton();
item = new UIMenuItem();
item.setValue(command.getLabel());
item.setActionExpression(Utility.createMethodExp(command.getCommandExpression()));
item.setValueExpression("disabled", Utility.createExpression(command.getDisabledExpression(), Boolean.class));
menuButton.getChildren().add(item);
where Utility.createMethodExpression is a static method (of my class Utility):
public static MethodExpression createMethodExp(String stringExpression) {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().getExpressionFactory().createMethodExpression(Utility.getELContext(),
stringExpression, Void.class, new Class[] {});
}
and Utility.createExpression is
public static ValueExpression createExpression(String stringExpression, Class<?> type) {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().getExpressionFactory().createValueExpression(context.getELContext(),
stringExpression, type);
}

Get bean class from composite component

I have composite component:
<my:component value="#{bean.property1.property2}/>
From composite component I need to get class of bean.property1 to read its annotations.
I do it by the following code:
ValueExpression valueExpression = expressionFactory.createValueExpression(FacesContext.getCurrentInstance().getELContext(),
"#{bean.property1}", Object.class);
Object bean = valueExpression.getValue(FacesContext.getCurrentInstance().getELContext());
Class<?> beanClass = bean.getClass();
This works well but if I use my:component from a facelet and pass bean as a parameter via ui:param this does not work because bean can't be resolved.
Probably I should use FaceletContext as ELContext instead of FacesContext.getCurrentInstance().getELContext():
FaceletContext faceletElContext = (FaceletContext) FacesContext.getCurrentInstance().getAttributes()
.get("javax.faces.FACELET_CONTEXT");
But this doesn't work on RENDER_RESPONSE phase (from encodeBegin method). It returns last used ELContext instead of actual context (I am not surprised :) ).
The goal is to get class of #{bean.property1} from my:component. How can I do it?
This is easy with RichFaces:
ValueExpressionAnalayser analyser = new ValueExpressionAnalayserImpl();
ValueDescriptor valueDescriptor = analyser.getPropertyDescriptor(context, valueExpression);
Class<?> beanClass = valueDescriptor.getBeanType();
This is ok for me.
Also there is ValueExpressionAnalayzer in javax.faces.validator package but it is package private and can't be used.
You could pass the bean as a parameter to the component.
1) Declare the attribute in the component interface file(if you are using composite component):
<cc:interface componentType="myComponentClass">
<cc:attribute name="myBean" preferred="true"/>
..others attributes
<cc:interface>
2) Implement the respective getter and setter for "myBean" attribute in the component class(myComponentClass)
protected enum PropertyKeys {
myBean;
String toString;
PropertyKeys(String toString) {
this.toString = toString;
}
PropertyKeys() {}
#Override
public String toString() {
return ((this.toString != null) ? this.toString : super.toString());
}
}
public YourBeanClass getMyBean() {
return (YourBeanClass) getStateHelper().eval(PropertyKeys.myBean, null);
}
public void setMyBean(YourBeanClass myBean) {
getStateHelper().put(PropertyKeys.myBean, myBean);
}
3) Set the attribute on you jsf page:
<my:component myBean="#{bean}"/>
4) In the component's render class cast the UIComponent to myComponentClass.
#Override
public void encodeBegin(FacesContext pContext, UIComponent pComponent)
throws IOException {
myComponentClass myComponent = (myComponentClass) pComponent;
myComponent.getYourAttribute();
}

How to get a UIComponent by its component id in icefaces

I'm trying to access an icefaces component, exactly an Accordion so i can set its activeIndex from my bean. the problem is that the returned value is always null. this is my code.
public static UIComponent findComponentInRoot(String id) {
UIComponent component = null;
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext != null) {
UIComponent root = facesContext.getViewRoot();
component = findComponent(root, id);
}
return component;
}
public static UIComponent findComponent(UIComponent base, String id) {
if (id.equals(base.getId()))
return base;
UIComponent kid = null;
UIComponent result = null;
Iterator kids = base.getFacetsAndChildren();
while (kids.hasNext() && (result == null)) {
kid = (UIComponent) kids.next();
if (id.equals(kid.getId())) {
result = kid;
break;
}
result = findComponent(kid, id);
if (result != null) {
break;
}
}
return result;
}
and i call this method like this:
Accordion acco = (Accordion)findComponentInRoot("menuFormId:menu");
my page look like this or to say a part of it:
<h:form id="menuFormId">
<icecore:singleSubmit />
<ace:accordion id="menu" collapsible="true" autoHeight="false" >
<ace:accordionPane id="system" title="#{msgs.LABEL_ADMINISTRATION}"
rendered="#{navigationCtrl.functionList['GESUTAD'] or navigationCtrl.functionList['GESPROF'] or navigationCtrl.functionList['GESUTTOM'] or navigationCtrl.functionList['SYNCPRC']}">
<div class="divLinkStyle">
<ice:commandLink rendered="#{navigationCtrl.functionList['GESPROF']}" styleClass="linkMenu" action="#{navigationCtrl.redirectConsulterProfil}"
onmouseover="this.style.backgroundColor='#DEEDF8'" onmouseout="this.style.backgroundColor='#FFFFFF'">
<h:graphicImage value="../resources/images/util.png" />
<h:outputLabel value="#{msgs.LABEL_GESTION_PROFIL}" style="cursor: pointer;" />
</ice:commandLink>
</div>
...
Any ideas ?
my bean is session scoped.
i'm using icefaces 3.3.0 and jsf 2.2
You're confusing component ID with client ID. You're passing a client ID "menuFormId:menu" instead of component ID "menu" to your utility method, while the utility method actually finds the component by component ID instead of client ID.
Just use UIViewRoot#findComponent().
public static UIComponent findComponentInRoot(String id) {
return FacesContext.getCurrentInstance().getViewRoot().findComponent(id);
}
Unrelated to the concrete problem. You're making here a design mistake. The model should not be interested in the view. It should be the other way round. Set the activeIndex as a bean property and let the view hook on it the usual way.
<ace:accordion ... activeIndex="#{bean.activeIndex}">
In any case you're trying to grab/create/bind/manipulate/whatever a physical UIComponent instance in a backing bean class, you should absolutely stop coding and think twice if you're really doing things the right way. Ask if necessary at Stack Overflow if you can't figure out the right way.

JSF ResponseWriter custom components

I know about startElement, endElement, and writeAttribute methods on ResponseWriter. My problem is that I want to for example output a h:commandLink by declaring it like HtmlCommandLink link = new HtmlCommandLink(); .
How can I output other UIComponents like this in my own component? I might want to use some RichFaces ajax stuff in my components aswell so hoping I can avoid making it all by scratch.
Edit: What I'm trying to do is create my own tag library with the following tag <myTags:commentTree>. Every comment have a reply button, when the reply button is clicked I render the reply form beneath the comment. Once that is rendered, I would like to output for example the richfaces <a4j:commandButton> component. This have to be done inside my own java tag file which Ive called for CommentsTreeUI.java.
Normally I output all my elements that display the forms and buttons with writer.startElement("input", myComponent); writer.writeAttribute("type", "button", null); but if I could instead do for example startElement("a4j:commandbutton", myComponent) that would help my ALOT since it has all the built in ajax features etc.
Any clues?
This problem was solved by adding new components by using
HtmlCommandButton button = new HtmlCommandButton();
button.encodeAll(context);
You can do something like this:
HtmlCommandLink link = new HtmlCommandLink();
getChildren().add(link);
It does depend on what you want to do with the child components though i.e. if you want them surrounded with custom HTML (in an HTML list, for example) you will need something a bit more complex.
One approach to making composite controls is to use the binding attribute to associate the tag with your own code:
<f:view>
<h:form>
<h:panelGroup binding="#{compositeControlBean.panelGrid}" />
</h:form>
</f:view>
The bean configuration in faces-config.xml:
<managed-bean>
<managed-bean-name>compositeControlBean</managed-bean-name>
<managed-bean-class>
composite.CompositeControlBean
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
The bean code:
/**
* Configure this bean in request scope as "compositeControlBean".
*/
public class CompositeControlBean {
private transient UIComponent panelGrid;
public UIComponent getPanelGrid() {
if (panelGrid == null) {
panelGrid = createCompositePanel();
}
return panelGrid;
}
public void setPanelGrid(UIComponent panelGrid) {
this.panelGrid = panelGrid;
}
private UIComponent createCompositePanel() {
initContextMemebers();
UIComponent commandLink = createCommandLink();
String id = view.createUniqueId();
UIComponent panelGrid = application
.createComponent("javax.faces.HtmlPanelGroup");
panelGrid.setId(id);
panelGrid.setRendererType("javax.faces.Group");
panelGrid.getChildren().add(commandLink);
return panelGrid;
}
private UIComponent createCommandLink() {
// create control
String id = view.createUniqueId();
UIComponent commandLink = application
.createComponent("javax.faces.HtmlCommandLink");
commandLink.setId(id);
commandLink.setRendererType("javax.faces.Link");
// set attributes (bind to printHello method)
Map<String, Object> attributes = commandLink
.getAttributes();
MethodExpression action = expressionFactory
.createMethodExpression(elContext,
"#{compositeControlBean.printHello}",
String.class, new Class<?>[0]);
attributes.put("value", "print hello");
attributes.put("actionExpression", action);
return commandLink;
}
private transient FacesContext context;
private transient Application application;
private transient ELContext elContext;
private transient ExpressionFactory expressionFactory;
private transient UIViewRoot view;
private void initContextMemebers() {
context = FacesContext.getCurrentInstance();
application = context.getApplication();
elContext = context.getELContext();
expressionFactory = application.getExpressionFactory();
view = context.getViewRoot();
}
public String printHello() {
System.out.println("Hello");
return null;
}
}

Resources