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 }