View Javadoc
1   /*
2    * $Id$
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts.faces.application;
23  
24  import java.beans.FeatureDescriptor;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.Map;
28  
29  import jakarta.el.CompositeELResolver;
30  import jakarta.el.ELContext;
31  import jakarta.el.ELException;
32  import jakarta.el.ELResolver;
33  import jakarta.el.PropertyNotWritableException;
34  
35  import org.apache.struts.action.DynaActionForm;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * Defines property resolution behavior on instances of {@link DynaActionForm}.
41   *
42   * <p>This resolver handles base objects of type {@code DynaActionForm}
43   * with requested property name {code map}.</p>
44   *
45   * <p>This resolver is in read-only mode, which means that {@link #isReadOnly}
46   * will always return {@code true} and {@link #setValue} will always throw
47   * {@code PropertyNotWritableException}.</p>
48   *
49   * <p>{@code ELResolver}s are combined together using
50   * {@link CompositeELResolver}s, to define rich semantics for evaluating
51   * an expression. See the JavaDocs for {@link ELResolver} for details.</p>
52   *
53   * @see CompositeELResolver
54   * @see ELResolver
55   * @see DynaActionForm#getMap()
56   * @since Struts 1.4.1
57   */
58  public class DynaActionFormELResolver extends ELResolver {
59  
60      /**
61       * The {@code Log} instance for this class.
62       */
63      private final Logger log =
64          LoggerFactory.getLogger(DynaActionFormELResolver.class);
65  
66      /**
67       * Creates a new read {@code DynaActionFormELResolver}.
68       */
69      public DynaActionFormELResolver() {
70          log.debug("Creating new Dyna-Action-From-ELResolver instance");
71      }
72  
73      /**
74       * If the base object is a {@code DynaActionForm} and the requested
75       * property name is {code map} then the type {@code Map.class} will be
76       * returned.
77       *
78       * <p>If the base is a {@code DynaActionForm} and the requested property
79       * name is {code map}, the {@code propertyResolved} property of the
80       * {@code ELContext} object must be set to {@code true} by this resolver,
81       * before returning. If this property is not {@code true} after this
82       * method is called, the caller should ignore the return value.</p>
83       *
84       * <p>Assuming the base is a {@code DynaActionForm} and the requested
85       * property name is {code map}, this method will always return
86       * {@code Map.class}. This is because a {@code DynaActionForm} with the
87       * property 'map' accepts only the object {@code Map} as the value for
88       * this given key.</p>
89       *
90       * @param context The context of this evaluation.
91       * @param base The base to analyze. Only bases of type
92       *     {@code DynaActionForm} are handled by this resolver.
93       * @param property The key to return the acceptable type for. Only keys
94       *     with value {@code map} are handled by this resolver.
95       *
96       * @return If the {@code propertyResolved} property of {@code ELContext}
97       *     was set to {@code true}, then the most general acceptable type;
98       *     otherwise undefined.
99       *
100      * @throws NullPointerException if context is {@code null}
101      * @throws ELException if an exception was thrown while performing
102      *     the property or variable resolution. The thrown exception
103      *     must be included as the cause property of this exception, if
104      *     available.
105      */
106     @Override
107     public Class<?> getType(ELContext context, Object base, Object property) {
108         if (context == null) {
109             throw new NullPointerException();
110         }
111 
112         if (test(base, property)) {
113             log.trace("Returning property-type '{}' for DynaActionForm '{}'",
114                 property, base);
115 
116             context.setPropertyResolved(true);
117             return Map.class;
118         }
119 
120         return null;
121     }
122 
123     /**
124      * If the base object is a {@code DynaActionForm} and the requested
125      * property name is {@code map}, returns the {@code map} of the
126      * {@code DynaActionForm}. Otherwise {@code null} is returned.
127      *
128      * <p>If the base is a {@code DynaActionForm} and the requested property
129      * name is {@code map}, the {@code propertyResolved} property of the
130      * {@code ELContext} object must be set to {@code true} by this resolver,
131      * before returning. If this property is not {@code true} after this method
132      * is called, the caller should ignore the return value.</p>
133      *
134      * <p>Just as in {@link DynaActionForm#getMap()}, just because {@code null}
135      * is returned doesn't mean there is no mapping for the key; it's also
136      * possible that the method explicitly returns {@code null}.</p>
137      *
138      * @param context The context of this evaluation.
139      * @param base The base to be analyzed. Only bases of type
140      *     {@code DynaActionForm} are handled by this resolver.
141      * @param property The key whose associated value is to be returned. Only keys
142      *     with value {@code map} are handled by this resolver.
143      *
144      * @return If the {@code propertyResolved} property of {@code ELContext}
145      *     was set to {@code true}, then the value associated with the given key.
146      *
147      * @throws NullPointerException if context is {@code null}.
148      * @throws ELException if an exception was thrown while performing
149      *     the property or variable resolution. The thrown exception
150      *     must be included as the cause property of this exception, if
151      *     available.
152      */
153     @Override
154     public Object getValue(ELContext context, Object base, Object property) {
155         if (context == null) {
156             throw new NullPointerException();
157         }
158 
159         if (test(base, property)) {
160             log.trace("Returning property-value '{}' for DynaActionForm '{}'",
161                 property, base);
162 
163             context.setPropertyResolved(true);
164             return (((DynaActionForm) base).getMap());
165         }
166 
167         return null;
168     }
169 
170     /**
171      * If the base is a {@code DynaActionForm} and the requested property
172      * name is {@code map}, this method always throw
173      * {@code PropertyNotWritableException}, because the {@code map}
174      * property is a ready-only property of the {@code DynaActionForm}
175      * class.
176      *
177      * @param context The context of this evaluation.
178      * @param base The base to be modified. Only bases of type
179      *     {@code DynaActionForm} are handled by this resolver.
180      * @param property The key with which the specified value is to be
181      *     associated. Only keys with value {@code map} are handled by
182      *     this resolver.
183      * @param value The value to be associated with the specified key.
184 
185      * @throws NullPointerException if context is {@code null}.
186      * @throws PropertyNotWritableException is always throw, because
187      *     the {@code map} property is a ready-only property of the
188      *     {@code DynaActionForm} class.
189      * @throws ELException if an exception was thrown while performing
190      *     the property or variable resolution. The thrown exception
191      *     must be included as the cause property of this exception, if
192      *     available.
193      */
194     @Override
195     public void setValue(ELContext context, Object base, Object property, Object value) {
196         if (context == null) {
197             throw new NullPointerException();
198         }
199 
200         if (test(base, property)) {
201             log.trace("Set property-value '{}' for DynaActionForm '{}'",
202                 property, base);
203 
204             throw new PropertyNotWritableException(property.toString());
205         }
206     }
207 
208     /**
209      * If the base is a {@code DynaActionForm} and the requested property
210      * name is {@code map}, returns whether a call to {@link #setValue}
211      * will always fail. This method always returns {@code true}, because
212      * the {@code map} property is a ready-only property of the
213      * {@code DynaActionForm} class.
214      *
215      * <p>If the base is a {@code DynaActionForm} and the requested property
216      * name is {@code map}, the {@code propertyResolved} property of the
217      * {@code ELContext} object must be set to {@code true} by this resolver,
218      * before returning. If this property is not {@code true} after this
219      * method is called, the caller should ignore the return value.</p>
220      *
221      * @param context The context of this evaluation.
222      * @param base The base to analyze. Only bases of type
223      *     {@code DynaActionForm} are handled by this resolver.
224      * @param property The key to return the read-only status for. Only keys
225      *     with value {@code map} are handled by this resolver.
226      *
227      * @return If the {@code propertyResolved} property of {@code ELContext}
228      *     was set to {@code true}, then {@code true} if calling the
229      *     {@code setValue} method will always fail or {@code false} if it
230      *     is possible that such a call may succeed; otherwise undefined.
231      *
232      * @throws NullPointerException if context is {@code null}
233      * @throws ELException if an exception was thrown while performing
234      *     the property or variable resolution. The thrown exception
235      *     must be included as the cause property of this exception, if
236      *     available.
237      */
238     @Override
239     public boolean isReadOnly(ELContext context, Object base, Object property) {
240         if (context == null) {
241             throw new NullPointerException();
242         }
243 
244         if (test(base, property)) {
245             log.trace("Return ready-only status for property '{}' for DynaActionForm '{}'",
246                 property, base);
247 
248             context.setPropertyResolved(true);
249             return true;
250         }
251 
252         return false;
253     }
254 
255     /**
256      * If the base object is a {@code DynaActionForm}, returns an
257      * {@code Iterator} containing the set of keys available in the
258      * {@code DynaActionForm}. Otherwise, returns {@code null}.
259      *
260      * <p>The {@code Iterator} returned must contain zero or more
261      * instances of {@link FeatureDescriptor}. Each info object
262      * contains information about a key in the Map, and is initialized
263      * as follows:</p>
264      * <ul>
265      *     <li>displayName - The return value of calling the
266      *         {@code toString} method on this key, or {@code null} if
267      *         the key is {@code null}.</li>
268      *     <li>name - Same as displayName property.</li>
269      *     <li>shortDescription - JavaDoc-Code of
270      *         {@link DynaActionForm#getMap()}</li>
271      *     <li>expert - {@code false}</li>
272      *     <li>hidden - {@code false}</li>
273      *     <li>preferred - {@code true}</li>
274      * </ul>
275      * In addition, the following named attributes must be set in the
276      * returned {@code FeatureDescriptor}s:
277      * <ul>
278      *     <li>{@link ELResolver#TYPE} - The return value of calling
279      *         the {@code getClass()} method on this key, or
280      *         {@code null} if the key is {@code null}.</li>
281      *     <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} -
282      *         {@code true}</li>
283      * </ul>
284      *
285      * @param context The context of this evaluation.
286      * @param base The object whose keys are to be iterated over. Only
287      *     bases of type {@code DynaActionForm} are handled by this
288      *     resolver.
289      *
290      * @return An {@code Iterator} containing zero or more (possibly
291      *     infinitely more) {@code FeatureDescriptor} objects, each
292      *     representing a key in this {@code DynaActionForm}, or
293      *     {@code null} if the base object is not a
294      *     {@code DynaActionForm}.
295      */
296     @Override
297     public Iterator<FeatureDescriptor> getFeatureDescriptors(
298             ELContext context,
299             Object base) {
300 
301         if (base instanceof DynaActionForm) {
302             log.trace("Get Feature-Descriptors for DynaActionForm '{}'", base);
303 
304             final FeatureDescriptor descriptor = new FeatureDescriptor();
305             descriptor.setName("map");
306             descriptor.setDisplayName("map");
307             descriptor.setExpert(false);
308             descriptor.setHidden(false);
309             descriptor.setPreferred(true);
310             descriptor.setShortDescription("Returns the Map containing the property "
311                     + "values. This is done mostly to facilitate accessing the "
312                     + "DynaActionForm through JavaBeans accessors, in order to use "
313                     + "the JavaServer Pages Standard Tag Library (JSTL).");
314             descriptor.setValue(TYPE, Map.class);
315             descriptor.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
316 
317             return Collections.singleton(descriptor).iterator();
318         }
319 
320         return null;
321     }
322 
323     /**
324      * If the base object is a {@code DynaActionForm}, returns the most
325      * general type that this resolver accepts for the {@code property}
326      * argument. Otherwise, returns {@code null}.
327      *
328      * <p>Assuming the base is a {@code DynaActionForm}, this method will
329      * always return {@code Object.class}. This is because any object is
330      * accepted as a key and is coerced into a string.</p>
331      *
332      * @param context The context of this evaluation.
333      * @param base The base to analyze. Only bases of type
334      *     {@code DynaActionForm} are handled by this resolver.
335      *
336      * @return {@code null} if base is not a {@code DynaActionForm}; otherwise
337      *     {@code Object.class}.
338      */
339     @Override
340     public Class<?> getCommonPropertyType(ELContext context, Object base) {
341         if (base instanceof DynaActionForm) {
342             log.trace("Get Common-Property-Type for DynaActionForm '{}'", base);
343 
344             return Object.class;
345         }
346         return null;
347     }
348 
349     /**
350      * If the base object is a {@code DynaActionForm} and the requested
351      * property name is {@code map}.
352      *
353      * @param base The requested base object. Only bases of type
354      *     {@code DynaActionForm} are handled by this resolver.
355      * @param property The requested key. Only keys
356      *     with value {@code map} are handled by this resolver.
357      *
358      * @return {@code true} if request could handle with this resolver.
359      */
360     private static boolean test(final Object base, final Object property) {
361         return base instanceof DynaActionForm && "map".equals(property);
362     }
363 }