View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.facelets;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.Visual;
24  import org.apache.myfaces.tobago.context.Markup;
25  import org.apache.myfaces.tobago.internal.util.StringUtils;
26  import org.apache.myfaces.tobago.util.ComponentUtils;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.el.ELContext;
31  import javax.el.ELException;
32  import javax.el.ExpressionFactory;
33  import javax.el.MethodExpression;
34  import javax.el.MethodInfo;
35  import javax.el.ValueExpression;
36  import javax.faces.FacesException;
37  import javax.faces.component.ActionSource;
38  import javax.faces.component.ActionSource2;
39  import javax.faces.component.EditableValueHolder;
40  import javax.faces.component.StateHolder;
41  import javax.faces.component.UIComponent;
42  import javax.faces.component.ValueHolder;
43  import javax.faces.context.FacesContext;
44  import javax.faces.convert.Converter;
45  import javax.faces.event.MethodExpressionActionListener;
46  import javax.faces.event.MethodExpressionValueChangeListener;
47  import javax.faces.validator.MethodExpressionValidator;
48  import javax.faces.view.facelets.ComponentHandler;
49  import javax.faces.view.facelets.FaceletContext;
50  import javax.faces.view.facelets.TagAttribute;
51  import javax.faces.view.facelets.TagConfig;
52  import javax.faces.view.facelets.TagException;
53  import javax.faces.view.facelets.TagHandler;
54  import java.beans.IntrospectionException;
55  import java.beans.PropertyDescriptor;
56  import java.lang.invoke.MethodHandles;
57  import java.util.Objects;
58  
59  public final class AttributeHandler extends TagHandler {
60  
61    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
62  
63    private final TagAttribute name;
64  
65    private final TagAttribute value;
66  
67    private final TagAttribute mode;
68  
69    public AttributeHandler(final TagConfig config) {
70      super(config);
71      this.name = getRequiredAttribute(Attributes.name.getName());
72      this.value = getRequiredAttribute(Attributes.value.getName());
73      this.mode = getAttribute(Attributes.mode.getName());
74    }
75  
76    @Override
77    public void apply(final FaceletContext faceletContext, final UIComponent parent) throws ELException {
78      if (parent == null) {
79        throw new TagException(tag, "Parent UIComponent was null");
80      }
81  
82      if (ComponentHandler.isNew(parent)) {
83  
84        if (mode != null) {
85          if ("isNotSet".equals(mode.getValue())) {
86            boolean result = false;
87            String expressionString = value.getValue();
88            if (!value.isLiteral()) {
89              while (isSimpleExpression(expressionString)) {
90                if (isMethodOrValueExpression(expressionString)) {
91                  final ValueExpression expression
92                      = faceletContext.getVariableMapper().resolveVariable(removeElParenthesis(expressionString));
93                  if (expression == null) {
94                    result = true;
95                    break;
96                  } else {
97                    expressionString = expression.getExpressionString();
98                  }
99                } else {
100                 result = false;
101                 break;
102               }
103             }
104           } else {
105             result = StringUtils.isEmpty(expressionString);
106           }
107           parent.getAttributes().put(name.getValue(), result);
108         } else if ("isSet".equals(mode.getValue())) {
109           boolean result = true;
110           String expressionString = value.getValue();
111           if (!value.isLiteral()) {
112             while (isSimpleExpression(expressionString)) {
113               if (isMethodOrValueExpression(expressionString)) {
114                 final ValueExpression expression
115                     = faceletContext.getVariableMapper().resolveVariable(removeElParenthesis(expressionString));
116                 if (expression == null) {
117                   result = false;
118                   break;
119                 } else {
120                   expressionString = expression.getExpressionString();
121                 }
122               } else {
123                 result = true;
124                 break;
125               }
126             }
127           } else {
128             result = StringUtils.isNotEmpty(expressionString);
129           }
130           parent.getAttributes().put(name.getValue(), result);
131         } else if ("action".equals(mode.getValue())) {
132           String expressionString = value.getValue();
133           while (isSimpleExpression(expressionString)) {
134             if (isMethodOrValueExpression(expressionString)) {
135               final ValueExpression expression
136                   = faceletContext.getVariableMapper().resolveVariable(removeElParenthesis(expressionString));
137               if (expression == null) {
138                 // when the action hasn't been set while using a composition.
139                 if (LOG.isDebugEnabled()) {
140                   LOG.debug("Variable can't be resolved: value='" + expressionString + "'");
141                 }
142                 expressionString = null;
143                 break;
144               } else {
145                 expressionString = expression.getExpressionString();
146               }
147             } else {
148               break;
149             }
150           }
151           if (expressionString != null) {
152             final ExpressionFactory expressionFactory = faceletContext.getExpressionFactory();
153             final MethodExpression action = new TagMethodExpression(value, expressionFactory.createMethodExpression(
154                 faceletContext, expressionString, String.class, ComponentUtils.ACTION_ARGS));
155             ((ActionSource2) parent).setActionExpression(action);
156           }
157         } else if ("actionListener".equals(mode.getValue())) {
158           String expressionString = value.getValue();
159           while (isSimpleExpression(expressionString)) {
160             if (isMethodOrValueExpression(expressionString)) {
161               final ValueExpression expression
162                   = faceletContext.getVariableMapper().resolveVariable(removeElParenthesis(expressionString));
163               if (expression == null) {
164                 if (LOG.isDebugEnabled()) {
165                   // when the action hasn't been set while using a composition.
166                   LOG.debug("Variable can't be resolved: value='" + expressionString + "'");
167                 }
168                 expressionString = null;
169                 break;
170               } else {
171                 expressionString = expression.getExpressionString();
172               }
173             } else {
174               LOG.warn("Only expressions are supported mode=actionListener value='" + expressionString + "'");
175               expressionString = null;
176               break;
177             }
178           }
179           if (expressionString != null) {
180             final ExpressionFactory expressionFactory = faceletContext.getExpressionFactory();
181             final MethodExpression actionListener
182                 = new TagMethodExpression(value, expressionFactory.createMethodExpression(
183                 faceletContext, expressionString, null, ComponentUtils.ACTION_LISTENER_ARGS));
184             ((ActionSource) parent).addActionListener(new MethodExpressionActionListener(actionListener));
185           }
186         } else if ("actionFromValue".equals(mode.getValue())) {
187           if (!value.isLiteral()) {
188             final String result = value.getValue(faceletContext);
189             parent.getAttributes().put(name.getValue(), new ConstantMethodExpression(result));
190           }
191         } else if ("valueIfSet".equals(mode.getValue())) {
192           String expressionString = value.getValue();
193           String lastExpressionString = null;
194           while (isMethodOrValueExpression(expressionString) && isSimpleExpression(expressionString)) {
195             final ValueExpression expression
196                 = faceletContext.getVariableMapper().resolveVariable(removeElParenthesis(expressionString));
197             if (expression != null) {
198               lastExpressionString = expressionString;
199               expressionString = expression.getExpressionString();
200             } else {
201               // restore last value
202               expressionString = lastExpressionString;
203               break;
204             }
205           }
206           if (expressionString != null) {
207             final String attributeName = name.getValue(faceletContext);
208             if (containsMethodOrValueExpression(expressionString)) {
209               final ValueExpression expression = value.getValueExpression(faceletContext, Object.class);
210               parent.setValueExpression(attributeName, expression);
211             } else {
212               final Object literalValue = getValue(faceletContext, parent, expressionString, attributeName);
213               parent.getAttributes().put(attributeName, literalValue);
214             }
215           }
216         } else {
217           throw new FacesException("Type " + mode + " not supported");
218         }
219       } else {
220 
221         final Attributes nameValue = Attributes.valueOfFailsafe(name.getValue(faceletContext));
222         if (Attributes.rendered == nameValue) {
223           if (value.isLiteral()) {
224             parent.setRendered(value.getBoolean(faceletContext));
225           } else {
226             parent.setValueExpression(nameValue.getName(), value.getValueExpression(faceletContext, Boolean.class));
227           }
228         } else if (Attributes.markup == nameValue) {
229           if (parent instanceof Visual) {
230             if (value.isLiteral()) {
231               ((Visual) parent).setMarkup(Markup.valueOf(value.getValue()));
232             } else {
233               final ValueExpression expression = value.getValueExpression(faceletContext, Object.class);
234               parent.setValueExpression(nameValue.getName(), expression);
235             }
236           } else {
237             LOG.error("Component is not instanceof Visual. Instance is: " + parent.getClass().getName());
238           }
239         } else if (parent instanceof EditableValueHolder && Attributes.validator == nameValue) {
240           final MethodExpression methodExpression
241               = getMethodExpression(faceletContext, null, ComponentUtils.VALIDATOR_ARGS);
242           if (methodExpression != null) {
243             ((EditableValueHolder) parent).addValidator(new MethodExpressionValidator(methodExpression));
244           }
245         } else if (parent instanceof EditableValueHolder && Attributes.valueChangeListener == nameValue) {
246           final MethodExpression methodExpression =
247               getMethodExpression(faceletContext, null, ComponentUtils.VALUE_CHANGE_LISTENER_ARGS);
248           if (methodExpression != null) {
249             ((EditableValueHolder) parent).addValueChangeListener(
250                 new MethodExpressionValueChangeListener(methodExpression));
251           }
252         } else if (parent instanceof ValueHolder && Attributes.converter == nameValue) {
253           setConverter(faceletContext, parent, nameValue.getName());
254         } else if (parent instanceof ActionSource && Attributes.action.equals(nameValue)) {
255           final MethodExpression action = getMethodExpression(faceletContext, String.class, ComponentUtils.ACTION_ARGS);
256           if (action != null) {
257             ((ActionSource2) parent).setActionExpression(action);
258           }
259         } else if (parent instanceof ActionSource && Attributes.actionListener == nameValue) {
260           final MethodExpression action
261               = getMethodExpression(faceletContext, null, ComponentUtils.ACTION_LISTENER_ARGS);
262           if (action != null) {
263             ((ActionSource) parent).addActionListener(new MethodExpressionActionListener(action));
264           }
265         } else if (nameValue != null && !parent.getAttributes().containsKey(nameValue.getName())) {
266           if (value.isLiteral()) {
267             parent.getAttributes().put(nameValue.getName(), value.getValue());
268           } else {
269             parent.setValueExpression(nameValue.getName(), value.getValueExpression(faceletContext, Object.class));
270           }
271         } else if (nameValue == null) {
272           LOG.warn("Null value for {}", name);
273         }
274       }
275     }
276   }
277 
278   private boolean isMethodOrValueExpression(final String string) {
279     return (string.startsWith("${") || string.startsWith("#{")) && string.endsWith("}");
280   }
281 
282   private boolean containsMethodOrValueExpression(final String string) {
283     return (string.contains("${") || string.contains("#{")) && string.contains("}");
284   }
285 
286   private boolean isSimpleExpression(final String string) {
287     return string.indexOf('.') < 0 && string.indexOf('[') < 0;
288   }
289 
290   private String removeElParenthesis(final String string) {
291     return string.substring(2, string.length() - 1);
292   }
293 
294   private ValueExpression getExpression(final FaceletContext faceletContext) {
295     final String myValue = removeElParenthesis(value.getValue());
296     return faceletContext.getVariableMapper().resolveVariable(myValue);
297   }
298 
299   private MethodExpression getMethodExpression(
300       final FaceletContext faceletContext, final Class returnType, final Class[] args) {
301     // in a composition may be we get the method expression string from the current variable mapper
302     // the expression can be empty
303     // in this case return nothing
304     if (value.getValue().startsWith("${")) {
305       final ValueExpression expression = getExpression(faceletContext);
306       if (expression != null) {
307         final ExpressionFactory expressionFactory = faceletContext.getExpressionFactory();
308         return new TagMethodExpression(value, expressionFactory.createMethodExpression(faceletContext,
309             expression.getExpressionString(), returnType, args));
310       } else {
311         return null;
312       }
313     } else {
314       return value.getMethodExpression(faceletContext, returnType, args);
315     }
316   }
317 
318   private Object getValue(
319       final FaceletContext faceletContext, final UIComponent parent, final String expressionString,
320       final String attributeName) {
321     Class type = Object.class;
322     try {
323       type = new PropertyDescriptor(attributeName, parent.getClass()).getReadMethod().getReturnType();
324     } catch (final IntrospectionException e) {
325       LOG.warn("Can't determine expected type", e);
326     }
327     final ExpressionFactory expressionFactory = faceletContext.getExpressionFactory();
328     final ValueExpression valueExpression = expressionFactory
329         .createValueExpression(faceletContext, expressionString, type);
330     return valueExpression.getValue(faceletContext);
331   }
332 
333   private void setConverter(final FaceletContext faceletContext, final UIComponent parent, final String nameValue) {
334     // in a composition may be we get the converter expression string from the current variable mapper
335     // the expression can be empty
336     // in this case return nothing
337     if (value.getValue().startsWith("${")) {
338       final ValueExpression expression = getExpression(faceletContext);
339       if (expression != null) {
340         setConverter(faceletContext, parent, nameValue, expression);
341       }
342     } else {
343       setConverter(faceletContext, parent, nameValue, value.getValueExpression(faceletContext, Object.class));
344     }
345   }
346 
347   private void setConverter(
348       final FaceletContext faceletContext, final UIComponent parent, final String nameValue,
349       final ValueExpression expression) {
350     if (expression.isLiteralText()) {
351       final Converter converter =
352           faceletContext.getFacesContext().getApplication().createConverter(expression.getExpressionString());
353       ((ValueHolder) parent).setConverter(converter);
354     } else {
355       parent.setValueExpression(nameValue, expression);
356     }
357   }
358 
359   private static class ConstantMethodExpression extends MethodExpression implements StateHolder {
360 
361     private String outcome;
362 
363     private boolean transientFlag;
364 
365     ConstantMethodExpression() {
366     }
367 
368     ConstantMethodExpression(final String outcome) {
369       this.outcome = outcome;
370     }
371 
372     @Override
373     public MethodInfo getMethodInfo(final ELContext context)
374         throws NullPointerException, ELException {
375       return null;
376     }
377 
378     @Override
379     public Object invoke(final ELContext context, final Object[] params)
380         throws NullPointerException, ELException {
381       return outcome;
382     }
383 
384     @Override
385     public boolean equals(final Object o) {
386       if (this == o) {
387         return true;
388       }
389       if (o == null || getClass() != o.getClass()) {
390         return false;
391       }
392 
393       final ConstantMethodExpression that = (ConstantMethodExpression) o;
394 
395       return Objects.equals(outcome, that.outcome);
396     }
397 
398     @Override
399     public int hashCode() {
400       return outcome.hashCode();
401     }
402 
403     @Override
404     public String getExpressionString() {
405       return outcome;
406     }
407 
408     @Override
409     public boolean isLiteralText() {
410       return true;
411     }
412 
413     @Override
414     public Object saveState(final FacesContext context) {
415       return outcome;
416     }
417 
418     @Override
419     public void restoreState(final FacesContext context, final Object state) {
420       this.outcome = (String) state;
421     }
422 
423     @Override
424     public void setTransient(final boolean transientFlagParameter) {
425       this.transientFlag = transientFlag;
426     }
427 
428     @Override
429     public boolean isTransient() {
430       return transientFlag;
431     }
432   }
433 }