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 }