http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java new file mode 100644 index 0000000..ad79542 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlTypeConverterWrapper.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.conversion.TypeConverter; + +import java.lang.reflect.Member; +import java.util.Map; + +/** + * Wraps an XWork type conversion class for as an OGNL TypeConverter + */ +public class OgnlTypeConverterWrapper implements ognl.TypeConverter { + + private TypeConverter typeConverter; + + public OgnlTypeConverterWrapper(TypeConverter converter) { + if (converter == null) { + throw new IllegalArgumentException("Wrapped type converter cannot be null"); + } + this.typeConverter = converter; + } + + public Object convertValue(Map context, Object target, Member member, + String propertyName, Object value, Class toType) { + return typeConverter.convertValue(context, target, member, propertyName, value, toType); + } + + public TypeConverter getTarget() { + return typeConverter; + } +}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java new file mode 100644 index 0000000..45e9992 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java @@ -0,0 +1,574 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import ognl.*; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + + +/** + * Utility class that provides common access to the Ognl APIs for + * setting and getting properties from objects (usually Actions). + * + * @author Jason Carreira + */ +public class OgnlUtil { + + private static final Logger LOG = LogManager.getLogger(OgnlUtil.class); + private ConcurrentMap<String, Object> expressions = new ConcurrentHashMap<>(); + private final ConcurrentMap<Class, BeanInfo> beanInfoCache = new ConcurrentHashMap<>(); + private TypeConverter defaultConverter; + + private boolean devMode = false; + private boolean enableExpressionCache = true; + private boolean enableEvalExpression; + + private Set<Class<?>> excludedClasses = new HashSet<>(); + private Set<Pattern> excludedPackageNamePatterns = new HashSet<>(); + + private Container container; + private boolean allowStaticMethodAccess; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.defaultConverter = new OgnlTypeConverterWrapper(conv); + } + + @Inject(XWorkConstants.DEV_MODE) + public void setDevMode(String mode) { + this.devMode = BooleanUtils.toBoolean(mode); + } + + @Inject(XWorkConstants.ENABLE_OGNL_EXPRESSION_CACHE) + public void setEnableExpressionCache(String cache) { + enableExpressionCache = BooleanUtils.toBoolean(cache); + } + + @Inject(value = XWorkConstants.ENABLE_OGNL_EVAL_EXPRESSION, required = false) + public void setEnableEvalExpression(String evalExpression) { + enableEvalExpression = "true".equals(evalExpression); + if(enableEvalExpression){ + LOG.warn("Enabling OGNL expression evaluation may introduce security risks " + + "(see http://struts.apache.org/release/2.3.x/docs/s2-013.html for further details)"); + } + } + + @Inject(value = XWorkConstants.OGNL_EXCLUDED_CLASSES, required = false) + public void setExcludedClasses(String commaDelimitedClasses) { + Set<String> classes = TextParseUtil.commaDelimitedStringToSet(commaDelimitedClasses); + for (String className : classes) { + try { + excludedClasses.add(Class.forName(className)); + } catch (ClassNotFoundException e) { + throw new ConfigurationException("Cannot load excluded class: " + className, e); + } + } + } + + @Inject(value = XWorkConstants.OGNL_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false) + public void setExcludedPackageName(String commaDelimitedPackagePatterns) { + Set<String> packagePatterns = TextParseUtil.commaDelimitedStringToSet(commaDelimitedPackagePatterns); + for (String pattern : packagePatterns) { + excludedPackageNamePatterns.add(Pattern.compile(pattern)); + } + } + + public Set<Class<?>> getExcludedClasses() { + return excludedClasses; + } + + public Set<Pattern> getExcludedPackageNamePatterns() { + return excludedPackageNamePatterns; + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + + @Inject(value = XWorkConstants.ALLOW_STATIC_METHOD_ACCESS, required = false) + public void setAllowStaticMethodAccess(String allowStaticMethodAccess) { + this.allowStaticMethodAccess = Boolean.parseBoolean(allowStaticMethodAccess); + } + + /** + * Sets the object's properties using the default type converter, defaulting to not throw + * exceptions for problems setting the properties. + * + * @param props the properties being set + * @param o the object + * @param context the action context + */ + public void setProperties(Map<String, ?> props, Object o, Map<String, Object> context) { + setProperties(props, o, context, false); + } + + /** + * Sets the object's properties using the default type converter. + * + * @param props the properties being set + * @param o the object + * @param context the action context + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the properties + */ + public void setProperties(Map<String, ?> props, Object o, Map<String, Object> context, boolean throwPropertyExceptions) throws ReflectionException{ + if (props == null) { + return; + } + + Ognl.setTypeConverter(context, getTypeConverterFromContext(context)); + + Object oldRoot = Ognl.getRoot(context); + Ognl.setRoot(context, o); + + for (Map.Entry<String, ?> entry : props.entrySet()) { + String expression = entry.getKey(); + internalSetProperty(expression, entry.getValue(), o, context, throwPropertyExceptions); + } + + Ognl.setRoot(context, oldRoot); + } + + /** + * Sets the properties on the object using the default context, defaulting to not throwing + * exceptions for problems setting the properties. + * + * @param properties + * @param o + */ + public void setProperties(Map<String, ?> properties, Object o) { + setProperties(properties, o, false); + } + + /** + * Sets the properties on the object using the default context. + * + * @param properties the property map to set on the object + * @param o the object to set the properties into + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the properties + */ + public void setProperties(Map<String, ?> properties, Object o, boolean throwPropertyExceptions) { + Map context = createDefaultContext(o, null); + setProperties(properties, o, context, throwPropertyExceptions); + } + + /** + * Sets the named property to the supplied value on the Object, defaults to not throwing + * property exceptions. + * + * @param name the name of the property to be set + * @param value the value to set into the named property + * @param o the object upon which to set the property + * @param context the context which may include the TypeConverter + */ + public void setProperty(String name, Object value, Object o, Map<String, Object> context) { + setProperty(name, value, o, context, false); + } + + /** + * Sets the named property to the supplied value on the Object. + * + * @param name the name of the property to be set + * @param value the value to set into the named property + * @param o the object upon which to set the property + * @param context the context which may include the TypeConverter + * @param throwPropertyExceptions boolean which tells whether it should throw exceptions for + * problems setting the property + */ + public void setProperty(String name, Object value, Object o, Map<String, Object> context, boolean throwPropertyExceptions) { + Ognl.setTypeConverter(context, getTypeConverterFromContext(context)); + + Object oldRoot = Ognl.getRoot(context); + Ognl.setRoot(context, o); + + internalSetProperty(name, value, o, context, throwPropertyExceptions); + + Ognl.setRoot(context, oldRoot); + } + + /** + * Looks for the real target with the specified property given a root Object which may be a + * CompoundRoot. + * + * @return the real target or null if no object can be found with the specified property + */ + public Object getRealTarget(String property, Map<String, Object> context, Object root) throws OgnlException { + //special keyword, they must be cutting the stack + if ("top".equals(property)) { + return root; + } + + if (root instanceof CompoundRoot) { + // find real target + CompoundRoot cr = (CompoundRoot) root; + + try { + for (Object target : cr) { + if (OgnlRuntime.hasSetProperty((OgnlContext) context, target, property) + || OgnlRuntime.hasGetProperty((OgnlContext) context, target, property) + || OgnlRuntime.getIndexedPropertyType((OgnlContext) context, target.getClass(), property) != OgnlRuntime.INDEXED_PROPERTY_NONE + ) { + return target; + } + } + } catch (IntrospectionException ex) { + throw new ReflectionException("Cannot figure out real target class", ex); + } + + return null; + } + + return root; + } + + /** + * Wrapper around Ognl.setValue() to handle type conversion for collection elements. + * Ideally, this should be handled by OGNL directly. + */ + public void setValue(String name, Map<String, Object> context, Object root, Object value) throws OgnlException { + setValue(name, context, root, value, true); + } + + protected void setValue(String name, final Map<String, Object> context, final Object root, final Object value, final boolean evalName) throws OgnlException { + compileAndExecute(name, context, new OgnlTask<Void>() { + public Void execute(Object tree) throws OgnlException { + if (!evalName && isEvalExpression(tree, context)) { + throw new OgnlException("Eval expression cannot be used as parameter name"); + } + Ognl.setValue(tree, context, root, value); + return null; + } + }); + } + + private boolean isEvalExpression(Object tree, Map<String, Object> context) throws OgnlException { + if (tree instanceof SimpleNode) { + SimpleNode node = (SimpleNode) tree; + OgnlContext ognlContext = null; + + if (context!=null && context instanceof OgnlContext) { + ognlContext = (OgnlContext) context; + } + return node.isEvalChain(ognlContext); + } + return false; + } + + public Object getValue(final String name, final Map<String, Object> context, final Object root) throws OgnlException { + return compileAndExecute(name, context, new OgnlTask<Object>() { + public Object execute(Object tree) throws OgnlException { + return Ognl.getValue(tree, context, root); + } + }); + } + + public Object getValue(final String name, final Map<String, Object> context, final Object root, final Class resultType) throws OgnlException { + return compileAndExecute(name, context, new OgnlTask<Object>() { + public Object execute(Object tree) throws OgnlException { + return Ognl.getValue(tree, context, root, resultType); + } + }); + } + + + public Object compile(String expression) throws OgnlException { + return compile(expression, null); + } + + private <T> Object compileAndExecute(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException { + Object tree; + if (enableExpressionCache) { + tree = expressions.get(expression); + if (tree == null) { + tree = Ognl.parseExpression(expression); + checkEnableEvalExpression(tree, context); + } + } else { + tree = Ognl.parseExpression(expression); + checkEnableEvalExpression(tree, context); + } + + final T exec = task.execute(tree); + // if cache is enabled and it's a valid expression, puts it in + if(enableExpressionCache) { + expressions.putIfAbsent(expression, tree); + } + return exec; + } + + public Object compile(String expression, Map<String, Object> context) throws OgnlException { + return compileAndExecute(expression,context,new OgnlTask<Object>() { + public Object execute(Object tree) throws OgnlException { + return tree; + } + }); + } + + private void checkEnableEvalExpression(Object tree, Map<String, Object> context) throws OgnlException { + if (!enableEvalExpression && isEvalExpression(tree, context)) { + throw new OgnlException("Eval expressions has been disabled!"); + } + } + + /** + * Copies the properties in the object "from" and sets them in the object "to" + * using specified type converter, or {@link com.opensymphony.xwork2.conversion.impl.XWorkConverter} if none + * is specified. + * + * @param from the source object + * @param to the target object + * @param context the action context we're running under + * @param exclusions collection of method names to excluded from copying ( can be null) + * @param inclusions collection of method names to included copying (can be null) + * note if exclusions AND inclusions are supplied and not null nothing will get copied. + */ + public void copy(final Object from, final Object to, final Map<String, Object> context, Collection<String> exclusions, Collection<String> inclusions) { + if (from == null || to == null) { + LOG.warn("Attempting to copy from or to a null source. This is illegal and is bein skipped. This may be due to an error in an OGNL expression, action chaining, or some other event."); + return; + } + + TypeConverter converter = getTypeConverterFromContext(context); + final Map contextFrom = createDefaultContext(from, null); + Ognl.setTypeConverter(contextFrom, converter); + final Map contextTo = createDefaultContext(to, null); + Ognl.setTypeConverter(contextTo, converter); + + PropertyDescriptor[] fromPds; + PropertyDescriptor[] toPds; + + try { + fromPds = getPropertyDescriptors(from); + toPds = getPropertyDescriptors(to); + } catch (IntrospectionException e) { + LOG.error("An error occurred", e); + return; + } + + Map<String, PropertyDescriptor> toPdHash = new HashMap<>(); + + for (PropertyDescriptor toPd : toPds) { + toPdHash.put(toPd.getName(), toPd); + } + + for (PropertyDescriptor fromPd : fromPds) { + if (fromPd.getReadMethod() != null) { + boolean copy = true; + if (exclusions != null && exclusions.contains(fromPd.getName())) { + copy = false; + } else if (inclusions != null && !inclusions.contains(fromPd.getName())) { + copy = false; + } + + if (copy) { + PropertyDescriptor toPd = toPdHash.get(fromPd.getName()); + if ((toPd != null) && (toPd.getWriteMethod() != null)) { + try { + compileAndExecute(fromPd.getName(), context, new OgnlTask<Object>() { + public Void execute(Object expr) throws OgnlException { + Object value = Ognl.getValue(expr, contextFrom, from); + Ognl.setValue(expr, contextTo, to, value); + return null; + } + }); + + } catch (OgnlException e) { + LOG.debug("Got OGNL exception", e); + } + } + + } + + } + + } + } + + + /** + * Copies the properties in the object "from" and sets them in the object "to" + * using specified type converter, or {@link com.opensymphony.xwork2.conversion.impl.XWorkConverter} if none + * is specified. + * + * @param from the source object + * @param to the target object + * @param context the action context we're running under + */ + public void copy(Object from, Object to, Map<String, Object> context) { + copy(from, to, context, null, null); + } + + /** + * Get's the java beans property descriptors for the given source. + * + * @param source the source object. + * @return property descriptors. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public PropertyDescriptor[] getPropertyDescriptors(Object source) throws IntrospectionException { + BeanInfo beanInfo = getBeanInfo(source); + return beanInfo.getPropertyDescriptors(); + } + + + /** + * Get's the java beans property descriptors for the given class. + * + * @param clazz the source object. + * @return property descriptors. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws IntrospectionException { + BeanInfo beanInfo = getBeanInfo(clazz); + return beanInfo.getPropertyDescriptors(); + } + + /** + * Creates a Map with read properties for the given source object. + * <p/> + * If the source object does not have a read property (i.e. write-only) then + * the property is added to the map with the value <code>here is no read method for property-name</code>. + * + * @param source the source object. + * @return a Map with (key = read property name, value = value of read property). + * @throws IntrospectionException is thrown if an exception occurs during introspection. + * @throws OgnlException is thrown by OGNL if the property value could not be retrieved + */ + public Map<String, Object> getBeanMap(final Object source) throws IntrospectionException, OgnlException { + Map<String, Object> beanMap = new HashMap<>(); + final Map sourceMap = createDefaultContext(source, null); + PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(source); + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + final String propertyName = propertyDescriptor.getDisplayName(); + Method readMethod = propertyDescriptor.getReadMethod(); + if (readMethod != null) { + final Object value = compileAndExecute(propertyName, null, new OgnlTask<Object>() { + public Object execute(Object expr) throws OgnlException { + return Ognl.getValue(expr, sourceMap, source); + } + }); + beanMap.put(propertyName, value); + } else { + beanMap.put(propertyName, "There is no read method for " + propertyName); + } + } + return beanMap; + } + + /** + * Get's the java bean info for the given source object. Calls getBeanInfo(Class c). + * + * @param from the source object. + * @return java bean info. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public BeanInfo getBeanInfo(Object from) throws IntrospectionException { + return getBeanInfo(from.getClass()); + } + + + /** + * Get's the java bean info for the given source. + * + * @param clazz the source class. + * @return java bean info. + * @throws IntrospectionException is thrown if an exception occurs during introspection. + */ + public BeanInfo getBeanInfo(Class clazz) throws IntrospectionException { + synchronized (beanInfoCache) { + BeanInfo beanInfo; + beanInfo = beanInfoCache.get(clazz); + if (beanInfo == null) { + beanInfo = Introspector.getBeanInfo(clazz, Object.class); + beanInfoCache.putIfAbsent(clazz, beanInfo); + } + return beanInfo; + } + } + + void internalSetProperty(String name, Object value, Object o, Map<String, Object> context, boolean throwPropertyExceptions) throws ReflectionException{ + try { + setValue(name, context, o, value); + } catch (OgnlException e) { + Throwable reason = e.getReason(); + String msg = "Caught OgnlException while setting property '" + name + "' on type '" + o.getClass().getName() + "'."; + Throwable exception = (reason == null) ? e : reason; + + if (throwPropertyExceptions) { + throw new ReflectionException(msg, exception); + } else if (devMode) { + LOG.warn(msg, exception); + } + } + } + + TypeConverter getTypeConverterFromContext(Map<String, Object> context) { + /*ValueStack stack = (ValueStack) context.get(ActionContext.VALUE_STACK); + Container cont = (Container)stack.getContext().get(ActionContext.CONTAINER); + if (cont != null) { + return new OgnlTypeConverterWrapper(cont.getInstance(XWorkConverter.class)); + } else { + throw new IllegalArgumentException("Cannot find type converter in context map"); + } + */ + return defaultConverter; + } + + protected Map createDefaultContext(Object root) { + return createDefaultContext(root, null); + } + + protected Map createDefaultContext(Object root, ClassResolver classResolver) { + ClassResolver resolver = classResolver; + if (resolver == null) { + resolver = container.getInstance(CompoundRootAccessor.class); + } + + SecurityMemberAccess memberAccess = new SecurityMemberAccess(allowStaticMethodAccess); + memberAccess.setExcludedClasses(excludedClasses); + memberAccess.setExcludedPackageNamePatterns(excludedPackageNamePatterns); + + return Ognl.createDefaultContext(root, resolver, defaultConverter, memberAccess); + } + + private interface OgnlTask<T> { + T execute(Object tree) throws OgnlException; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java new file mode 100644 index 0000000..af7fbc5 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java @@ -0,0 +1,479 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.MemberAccessValueStack; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.*; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Ognl implementation of a value stack that allows for dynamic Ognl expressions to be evaluated against it. When evaluating an expression, + * the stack will be searched down the stack, from the latest objects pushed in to the earliest, looking for a bean with a getter or setter + * for the given property or a method of the given name (depending on the expression being evaluated). + * + * @author Patrick Lightbody + * @author tm_jee + * @version $Date$ $Id$ + */ +public class OgnlValueStack implements Serializable, ValueStack, ClearableValueStack, MemberAccessValueStack { + + public static final String THROW_EXCEPTION_ON_FAILURE = OgnlValueStack.class.getName() + ".throwExceptionOnFailure"; + + private static final long serialVersionUID = 370737852934925530L; + + private static final String MAP_IDENTIFIER_KEY = "com.opensymphony.xwork2.util.OgnlValueStack.MAP_IDENTIFIER_KEY"; + private static final Logger LOG = LogManager.getLogger(OgnlValueStack.class); + + CompoundRoot root; + transient Map<String, Object> context; + Class defaultType; + Map<Object, Object> overrides; + transient OgnlUtil ognlUtil; + transient SecurityMemberAccess securityMemberAccess; + private transient XWorkConverter converter; + + private boolean devMode; + private boolean logMissingProperties; + + protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) { + setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess); + push(prov); + } + + protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticAccess) { + setRoot(xworkConverter, accessor, new CompoundRoot(vs.getRoot()), allowStaticAccess); + } + + @Inject + public void setOgnlUtil(OgnlUtil ognlUtil) { + this.ognlUtil = ognlUtil; + securityMemberAccess.setExcludedClasses(ognlUtil.getExcludedClasses()); + securityMemberAccess.setExcludedPackageNamePatterns(ognlUtil.getExcludedPackageNamePatterns()); + } + + protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, + boolean allowStaticMethodAccess) { + this.root = compoundRoot; + this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess); + this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess); + context.put(VALUE_STACK, this); + Ognl.setClassResolver(context, accessor); + ((OgnlContext) context).setTraceEvaluations(false); + ((OgnlContext) context).setKeepLastEvaluation(false); + } + + @Inject(XWorkConstants.DEV_MODE) + public void setDevMode(String mode) { + this.devMode = BooleanUtils.toBoolean(mode); + } + + @Inject(value = "logMissingProperties", required = false) + public void setLogMissingProperties(String logMissingProperties) { + this.logMissingProperties = BooleanUtils.toBoolean(logMissingProperties); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#getContext() + */ + public Map<String, Object> getContext() { + return context; + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#setDefaultType(java.lang.Class) + */ + public void setDefaultType(Class defaultType) { + this.defaultType = defaultType; + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#setExprOverrides(java.util.Map) + */ + public void setExprOverrides(Map<Object, Object> overrides) { + if (this.overrides == null) { + this.overrides = overrides; + } else { + this.overrides.putAll(overrides); + } + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#getExprOverrides() + */ + public Map<Object, Object> getExprOverrides() { + return this.overrides; + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#getRoot() + */ + public CompoundRoot getRoot() { + return root; + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#setParameter(String, Object) + */ + public void setParameter(String expr, Object value) { + setValue(expr, value, devMode, false); + } + + /** + + /** + * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object) + */ + public void setValue(String expr, Object value) { + setValue(expr, value, devMode); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object, boolean) + */ + public void setValue(String expr, Object value, boolean throwExceptionOnFailure) { + setValue(expr, value, throwExceptionOnFailure, true); + } + + private void setValue(String expr, Object value, boolean throwExceptionOnFailure, boolean evalExpression) { + Map<String, Object> context = getContext(); + try { + trySetValue(expr, value, throwExceptionOnFailure, context, evalExpression); + } catch (OgnlException e) { + handleOgnlException(expr, value, throwExceptionOnFailure, e); + } catch (RuntimeException re) { //XW-281 + handleRuntimeException(expr, value, throwExceptionOnFailure, re); + } finally { + cleanUpContext(context); + } + } + + private void trySetValue(String expr, Object value, boolean throwExceptionOnFailure, Map<String, Object> context, boolean evalExpression) throws OgnlException { + context.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, expr); + context.put(REPORT_ERRORS_ON_NO_PROP, (throwExceptionOnFailure) ? Boolean.TRUE : Boolean.FALSE); + ognlUtil.setValue(expr, context, root, value, evalExpression); + } + + private void cleanUpContext(Map<String, Object> context) { + ReflectionContextState.clear(context); + context.remove(XWorkConverter.CONVERSION_PROPERTY_FULLNAME); + context.remove(REPORT_ERRORS_ON_NO_PROP); + } + + private void handleRuntimeException(String expr, Object value, boolean throwExceptionOnFailure, RuntimeException re) { + if (throwExceptionOnFailure) { + String message = ErrorMessageBuilder.create() + .errorSettingExpressionWithValue(expr, value) + .build(); + throw new XWorkException(message, re); + } else { + LOG.warn("Error setting value [{}] with expression [{}]", value, expr, re); + } + } + + private void handleOgnlException(String expr, Object value, boolean throwExceptionOnFailure, OgnlException e) { + boolean shouldLog = shouldLogMissingPropertyWarning(e); + String msg = null; + if (throwExceptionOnFailure || shouldLog) { + msg = ErrorMessageBuilder.create().errorSettingExpressionWithValue(expr, value).build(); + } + if (shouldLog) { + LOG.warn(msg, e); + } + + if (throwExceptionOnFailure) { + throw new XWorkException(msg, e); + } + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#findString(java.lang.String) + */ + public String findString(String expr) { + return (String) findValue(expr, String.class); + } + + public String findString(String expr, boolean throwExceptionOnFailure) { + return (String) findValue(expr, String.class, throwExceptionOnFailure); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String) + */ + public Object findValue(String expr, boolean throwExceptionOnFailure) { + try { + setupExceptionOnFailure(throwExceptionOnFailure); + return tryFindValueWhenExpressionIsNotNull(expr); + } catch (OgnlException e) { + return handleOgnlException(expr, throwExceptionOnFailure, e); + } catch (Exception e) { + return handleOtherException(expr, throwExceptionOnFailure, e); + } finally { + ReflectionContextState.clear(context); + } + } + + private void setupExceptionOnFailure(boolean throwExceptionOnFailure) { + if (throwExceptionOnFailure) { + context.put(THROW_EXCEPTION_ON_FAILURE, true); + } + } + + private Object tryFindValueWhenExpressionIsNotNull(String expr) throws OgnlException { + if (expr == null) { + return null; + } + return tryFindValue(expr); + } + + private Object handleOtherException(String expr, boolean throwExceptionOnFailure, Exception e) { + logLookupFailure(expr, e); + + if (throwExceptionOnFailure) + throw new XWorkException(e); + + return findInContext(expr); + } + + private Object tryFindValue(String expr) throws OgnlException { + Object value; + expr = lookupForOverrides(expr); + if (defaultType != null) { + value = findValue(expr, defaultType); + } else { + value = getValueUsingOgnl(expr); + if (value == null) { + value = findInContext(expr); + } + } + return value; + } + + private String lookupForOverrides(String expr) { + if ((overrides != null) && overrides.containsKey(expr)) { + expr = (String) overrides.get(expr); + } + return expr; + } + + private Object getValueUsingOgnl(String expr) throws OgnlException { + try { + return ognlUtil.getValue(expr, context, root); + } finally { + context.remove(THROW_EXCEPTION_ON_FAILURE); + } + } + + public Object findValue(String expr) { + return findValue(expr, false); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#findValue(java.lang.String, java.lang.Class) + */ + public Object findValue(String expr, Class asType, boolean throwExceptionOnFailure) { + try { + setupExceptionOnFailure(throwExceptionOnFailure); + return tryFindValueWhenExpressionIsNotNull(expr, asType); + } catch (OgnlException e) { + final Object value = handleOgnlException(expr, throwExceptionOnFailure, e); + return converter.convertValue(getContext(), value, asType); + } catch (Exception e) { + final Object value = handleOtherException(expr, throwExceptionOnFailure, e); + return converter.convertValue(getContext(), value, asType); + } finally { + ReflectionContextState.clear(context); + } + } + + private Object tryFindValueWhenExpressionIsNotNull(String expr, Class asType) throws OgnlException { + if (expr == null) { + return null; + } + return tryFindValue(expr, asType); + } + + private Object handleOgnlException(String expr, boolean throwExceptionOnFailure, OgnlException e) { + Object ret = findInContext(expr); + if (ret == null) { + if (shouldLogMissingPropertyWarning(e)) { + LOG.warn("Could not find property [{}]!", expr, e); + } + if (throwExceptionOnFailure) { + throw new XWorkException(e); + } + } + return ret; + } + + private boolean shouldLogMissingPropertyWarning(OgnlException e) { + return (e instanceof NoSuchPropertyException || e instanceof MethodFailedException) + && devMode && logMissingProperties; + } + + private Object tryFindValue(String expr, Class asType) throws OgnlException { + Object value = null; + try { + expr = lookupForOverrides(expr); + value = getValue(expr, asType); + if (value == null) { + value = findInContext(expr); + return converter.convertValue(getContext(), value, asType); + } + } finally { + context.remove(THROW_EXCEPTION_ON_FAILURE); + } + return value; + } + + private Object getValue(String expr, Class asType) throws OgnlException { + return ognlUtil.getValue(expr, context, root, asType); + } + + private Object findInContext(String name) { + return getContext().get(name); + } + + public Object findValue(String expr, Class asType) { + return findValue(expr, asType, false); + } + + /** + * Log a failed lookup, being more verbose when devMode=true. + * + * @param expr The failed expression + * @param e The thrown exception. + */ + private void logLookupFailure(String expr, Exception e) { + if (devMode && LOG.isWarnEnabled()) { + LOG.warn("Caught an exception while evaluating expression '{}' against value stack", expr, e); + LOG.warn("NOTE: Previous warning message was issued due to devMode set to true."); + } else { + LOG.debug("Caught an exception while evaluating expression '{}' against value stack", expr, e); + } + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#peek() + */ + public Object peek() { + return root.peek(); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#pop() + */ + public Object pop() { + return root.pop(); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#push(java.lang.Object) + */ + public void push(Object o) { + root.push(o); + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#set(java.lang.String, java.lang.Object) + */ + public void set(String key, Object o) { + //set basically is backed by a Map pushed on the stack with a key being put on the map and the Object being the value + Map setMap = retrieveSetMap(); + setMap.put(key, o); + } + + private Map retrieveSetMap() { + Map setMap; + Object topObj = peek(); + if (shouldUseOldMap(topObj)) { + setMap = (Map) topObj; + } else { + setMap = new HashMap(); + setMap.put(MAP_IDENTIFIER_KEY, ""); + push(setMap); + } + return setMap; + } + + /** + * check if this is a Map put on the stack for setting if so just use the old map (reduces waste) + */ + private boolean shouldUseOldMap(Object topObj) { + return topObj instanceof Map && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null; + } + + /** + * @see com.opensymphony.xwork2.util.ValueStack#size() + */ + public int size() { + return root.size(); + } + + private Object readResolve() { + // TODO: this should be done better + ActionContext ac = ActionContext.getContext(); + Container cont = ac.getContainer(); + XWorkConverter xworkConverter = cont.getInstance(XWorkConverter.class); + CompoundRootAccessor accessor = (CompoundRootAccessor) cont.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()); + TextProvider prov = cont.getInstance(TextProvider.class, "system"); + boolean allow = BooleanUtils.toBoolean(cont.getInstance(String.class, XWorkConstants.ALLOW_STATIC_METHOD_ACCESS)); + OgnlValueStack aStack = new OgnlValueStack(xworkConverter, accessor, prov, allow); + aStack.setOgnlUtil(cont.getInstance(OgnlUtil.class)); + aStack.setRoot(xworkConverter, accessor, this.root, allow); + + return aStack; + } + + + public void clearContextValues() { + //this is an OGNL ValueStack so the context will be an OgnlContext + //it would be better to make context of type OgnlContext + ((OgnlContext) context).getValues().clear(); + } + + public void setAcceptProperties(Set<Pattern> acceptedProperties) { + securityMemberAccess.setAcceptProperties(acceptedProperties); + } + + public void setExcludeProperties(Set<Pattern> excludeProperties) { + securityMemberAccess.setExcludeProperties(excludeProperties); + } + + @Inject + public void setXWorkConverter(final XWorkConverter converter) { + this.converter = converter; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java new file mode 100644 index 0000000..7617494 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStackFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.conversion.NullHandler; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import ognl.MethodAccessor; +import ognl.OgnlRuntime; +import ognl.PropertyAccessor; +import org.apache.commons.lang3.BooleanUtils; + +import java.util.Map; +import java.util.Set; + +/** + * Creates an Ognl value stack + */ +public class OgnlValueStackFactory implements ValueStackFactory { + + private XWorkConverter xworkConverter; + private CompoundRootAccessor compoundRootAccessor; + private TextProvider textProvider; + private Container container; + private boolean allowStaticMethodAccess; + + @Inject + public void setXWorkConverter(XWorkConverter converter) { + this.xworkConverter = converter; + } + + @Inject("system") + public void setTextProvider(TextProvider textProvider) { + this.textProvider = textProvider; + } + + @Inject(value="allowStaticMethodAccess", required=false) + public void setAllowStaticMethodAccess(String allowStaticMethodAccess) { + this.allowStaticMethodAccess = BooleanUtils.toBoolean(allowStaticMethodAccess); + } + + public ValueStack createValueStack() { + ValueStack stack = new OgnlValueStack(xworkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess); + container.inject(stack); + stack.getContext().put(ActionContext.CONTAINER, container); + return stack; + } + + public ValueStack createValueStack(ValueStack stack) { + ValueStack result = new OgnlValueStack(stack, xworkConverter, compoundRootAccessor, allowStaticMethodAccess); + container.inject(result); + stack.getContext().put(ActionContext.CONTAINER, container); + return result; + } + + @Inject + public void setContainer(Container container) throws ClassNotFoundException { + Set<String> names = container.getInstanceNames(PropertyAccessor.class); + for (String name : names) { + Class cls = Class.forName(name); + if (cls != null) { + if (Map.class.isAssignableFrom(cls)) { + PropertyAccessor acc = container.getInstance(PropertyAccessor.class, name); + } + OgnlRuntime.setPropertyAccessor(cls, container.getInstance(PropertyAccessor.class, name)); + if (compoundRootAccessor == null && CompoundRoot.class.isAssignableFrom(cls)) { + compoundRootAccessor = (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, name); + } + } + } + + names = container.getInstanceNames(MethodAccessor.class); + for (String name : names) { + Class cls = Class.forName(name); + if (cls != null) { + OgnlRuntime.setMethodAccessor(cls, container.getInstance(MethodAccessor.class, name)); + } + } + + names = container.getInstanceNames(NullHandler.class); + for (String name : names) { + Class cls = Class.forName(name); + if (cls != null) { + OgnlRuntime.setNullHandler(cls, new OgnlNullHandlerWrapper(container.getInstance(NullHandler.class, name))); + } + } + if (compoundRootAccessor == null) { + throw new IllegalStateException("Couldn't find the compound root accessor"); + } + this.container = container; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java new file mode 100644 index 0000000..2afd3d6 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java @@ -0,0 +1,194 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import ognl.DefaultMemberAccess; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Allows access decisions to be made on the basis of whether a member is static or not. + * Also blocks or allows access to properties. + */ +public class SecurityMemberAccess extends DefaultMemberAccess { + + private static final Logger LOG = LogManager.getLogger(SecurityMemberAccess.class); + + private final boolean allowStaticMethodAccess; + private Set<Pattern> excludeProperties = Collections.emptySet(); + private Set<Pattern> acceptProperties = Collections.emptySet(); + private Set<Class<?>> excludedClasses = Collections.emptySet(); + private Set<Pattern> excludedPackageNamePatterns = Collections.emptySet(); + + public SecurityMemberAccess(boolean method) { + super(false); + allowStaticMethodAccess = method; + } + + public boolean getAllowStaticMethodAccess() { + return allowStaticMethodAccess; + } + + @Override + public boolean isAccessible(Map context, Object target, Member member, String propertyName) { + if (checkEnumAccess(target, member)) { + LOG.trace("Allowing access to enum: {}", target); + return true; + } + + Class targetClass = target.getClass(); + Class memberClass = member.getDeclaringClass(); + + if (Modifier.isStatic(member.getModifiers()) && allowStaticMethodAccess) { + LOG.debug("Support for accessing static methods [target: {}, member: {}, property: {}] is deprecated!", target, member, propertyName); + if (!isClassExcluded(member.getDeclaringClass())) { + targetClass = member.getDeclaringClass(); + } + } + + if (isPackageExcluded(targetClass.getPackage(), memberClass.getPackage())) { + LOG.warn("Package of target [{}] or package of member [{}] are excluded!", target, member); + return false; + } + + if (isClassExcluded(targetClass)) { + LOG.warn("Target class [{}] is excluded!", target); + return false; + } + + if (isClassExcluded(memberClass)) { + LOG.warn("Declaring class of member type [{}] is excluded!", member); + return false; + } + + boolean allow = true; + if (!checkStaticMethodAccess(member)) { + LOG.warn("Access to static [{}] is blocked!", member); + allow = false; + } + + //failed static test + if (!allow) { + return false; + } + + // Now check for standard scope rules + return super.isAccessible(context, target, member, propertyName) && isAcceptableProperty(propertyName); + } + + protected boolean checkStaticMethodAccess(Member member) { + int modifiers = member.getModifiers(); + if (Modifier.isStatic(modifiers)) { + return allowStaticMethodAccess; + } else { + return true; + } + } + + protected boolean checkEnumAccess(Object target, Member member) { + if (target instanceof Class) { + Class clazz = (Class) target; + if (Enum.class.isAssignableFrom(clazz) && member.getName().equals("values")) { + return true; + } + } + return false; + } + + protected boolean isPackageExcluded(Package targetPackage, Package memberPackage) { + if (targetPackage == null || memberPackage == null) { + LOG.warn("The use of the default (unnamed) package is discouraged!"); + } + + final String targetPackageName = targetPackage == null ? "" : targetPackage.getName(); + final String memberPackageName = memberPackage == null ? "" : memberPackage.getName(); + for (Pattern pattern : excludedPackageNamePatterns) { + if (pattern.matcher(targetPackageName).matches() || pattern.matcher(memberPackageName).matches()) { + return true; + } + } + return false; + } + + protected boolean isClassExcluded(Class<?> clazz) { + if (clazz == Object.class) { + return true; + } + for (Class<?> excludedClass : excludedClasses) { + if (clazz.isAssignableFrom(excludedClass)) { + return true; + } + } + return false; + } + + protected boolean isAcceptableProperty(String name) { + return name == null || ((!isExcluded(name)) && isAccepted(name)); + } + + protected boolean isAccepted(String paramName) { + if (!this.acceptProperties.isEmpty()) { + for (Pattern pattern : acceptProperties) { + Matcher matcher = pattern.matcher(paramName); + if (matcher.matches()) { + return true; + } + } + + //no match, but acceptedParams is not empty + return false; + } + + //empty acceptedParams + return true; + } + + protected boolean isExcluded(String paramName) { + if (!this.excludeProperties.isEmpty()) { + for (Pattern pattern : excludeProperties) { + Matcher matcher = pattern.matcher(paramName); + if (matcher.matches()) { + return true; + } + } + } + return false; + } + + public void setExcludeProperties(Set<Pattern> excludeProperties) { + this.excludeProperties = excludeProperties; + } + + public void setAcceptProperties(Set<Pattern> acceptedProperties) { + this.acceptProperties = acceptedProperties; + } + + public void setExcludedClasses(Set<Class<?>> excludedClasses) { + this.excludedClasses = excludedClasses; + } + + public void setExcludedPackageNamePatterns(Set<Pattern> excludedPackageNamePatterns) { + this.excludedPackageNamePatterns = excludedPackageNamePatterns; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java b/core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java new file mode 100644 index 0000000..dbf21f6 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/XWorkTypeConverterWrapper.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2007,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl; + +import com.opensymphony.xwork2.conversion.TypeConverter; + +import java.lang.reflect.Member; +import java.util.Map; + +/** + * Wraps an OGNL TypeConverter as an XWork TypeConverter + */ +public class XWorkTypeConverterWrapper implements TypeConverter { + + private ognl.TypeConverter typeConverter; + + public XWorkTypeConverterWrapper(ognl.TypeConverter conv) { + this.typeConverter = conv; + } + + public Object convertValue(Map context, Object target, Member member, + String propertyName, Object value, Class toType) { + return typeConverter.convertValue(context, target, member, propertyName, value, toType); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java new file mode 100644 index 0000000..3beb14a --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java @@ -0,0 +1,327 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import ognl.*; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import static java.lang.String.format; +import static org.apache.commons.lang3.BooleanUtils.toBoolean; + +/** + * A stack that is able to call methods on objects in the stack. + * + * @author $Author$ + * @author Rainer Hermanns + * @version $Revision$ + */ +public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, ClassResolver { + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceAccessor(OgnlContext context, Object target, Object index) { + return null; + } + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceSetter(OgnlContext context, Object target, Object index) { + return null; + } + + private final static Logger LOG = LogManager.getLogger(CompoundRootAccessor.class); + private final static Class[] EMPTY_CLASS_ARRAY = new Class[0]; + private static Map<MethodCall, Boolean> invalidMethods = new ConcurrentHashMap<>(); + private boolean devMode = false; + + @Inject(XWorkConstants.DEV_MODE) + public void setDevMode(String mode) { + this.devMode = BooleanUtils.toBoolean(mode); + } + + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + CompoundRoot root = (CompoundRoot) target; + OgnlContext ognlContext = (OgnlContext) context; + + for (Object o : root) { + if (o == null) { + continue; + } + + try { + if (OgnlRuntime.hasSetProperty(ognlContext, o, name)) { + OgnlRuntime.setProperty(ognlContext, o, name, value); + + return; + } else if (o instanceof Map) { + @SuppressWarnings("unchecked") + Map<Object, Object> map = (Map<Object, Object>) o; + try { + map.put(name, value); + return; + } catch (UnsupportedOperationException e) { + // This is an unmodifiable Map, so move on to the next element in the stack + } + } +// } catch (OgnlException e) { +// if (e.getReason() != null) { +// final String msg = "Caught an Ognl exception while setting property " + name; +// log.error(msg, e); +// throw new RuntimeException(msg, e.getReason()); +// } + } catch (IntrospectionException e) { + // this is OK if this happens, we'll just keep trying the next + } + } + + boolean reportError = toBoolean((Boolean) context.get(ValueStack.REPORT_ERRORS_ON_NO_PROP)); + + if (reportError || devMode) { + final String msg = format("No object in the CompoundRoot has a publicly accessible property named '%s' " + + "(no setter could be found).", name); + if (reportError) { + throw new XWorkException(msg); + } else { + LOG.warn(msg); + } + } + } + + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + CompoundRoot root = (CompoundRoot) target; + OgnlContext ognlContext = (OgnlContext) context; + + if (name instanceof Integer) { + Integer index = (Integer) name; + return root.cutStack(index); + } else if (name instanceof String) { + if ("top".equals(name)) { + if (root.size() > 0) { + return root.get(0); + } else { + return null; + } + } + + for (Object o : root) { + if (o == null) { + continue; + } + + try { + if ((OgnlRuntime.hasGetProperty(ognlContext, o, name)) || ((o instanceof Map) && ((Map) o).containsKey(name))) { + return OgnlRuntime.getProperty(ognlContext, o, name); + } + } catch (OgnlException e) { + if (e.getReason() != null) { + final String msg = "Caught an Ognl exception while getting property " + name; + throw new XWorkException(msg, e); + } + } catch (IntrospectionException e) { + // this is OK if this happens, we'll just keep trying the next + } + } + + //property was not found + if (context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE)) + throw new NoSuchPropertyException(target, name); + else + return null; + } else { + return null; + } + } + + public Object callMethod(Map context, Object target, String name, Object[] objects) throws MethodFailedException { + CompoundRoot root = (CompoundRoot) target; + + if ("describe".equals(name)) { + Object v; + if (objects != null && objects.length == 1) { + v = objects[0]; + } else { + v = root.get(0); + } + + + if (v instanceof Collection || v instanceof Map || v.getClass().isArray()) { + return v.toString(); + } + + try { + Map<String, PropertyDescriptor> descriptors = OgnlRuntime.getPropertyDescriptors(v.getClass()); + + int maxSize = 0; + for (String pdName : descriptors.keySet()) { + if (pdName.length() > maxSize) { + maxSize = pdName.length(); + } + } + + SortedSet<String> set = new TreeSet<>(); + StringBuffer sb = new StringBuffer(); + for (PropertyDescriptor pd : descriptors.values()) { + + sb.append(pd.getName()).append(": "); + int padding = maxSize - pd.getName().length(); + for (int i = 0; i < padding; i++) { + sb.append(" "); + } + sb.append(pd.getPropertyType().getName()); + set.add(sb.toString()); + + sb = new StringBuffer(); + } + + sb = new StringBuffer(); + for (Object aSet : set) { + String s = (String) aSet; + sb.append(s).append("\n"); + } + + return sb.toString(); + } catch (IntrospectionException | OgnlException e) { + LOG.debug("Got exception in callMethod", e); + } + return null; + } + + for (Object o : root) { + if (o == null) { + continue; + } + + Class clazz = o.getClass(); + Class[] argTypes = getArgTypes(objects); + + MethodCall mc = null; + + if (argTypes != null) { + mc = new MethodCall(clazz, name, argTypes); + } + + if ((argTypes == null) || !invalidMethods.containsKey(mc)) { + try { + Object value = OgnlRuntime.callMethod((OgnlContext) context, o, name, objects); + + if (value != null) { + return value; + } + } catch (OgnlException e) { + // try the next one + Throwable reason = e.getReason(); + + if (!context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE) && (mc != null) && (reason != null) && (reason.getClass() == NoSuchMethodException.class)) { + invalidMethods.put(mc, Boolean.TRUE); + } else if (reason != null) { + throw new MethodFailedException(o, name, e.getReason()); + } + } + } + } + + return null; + } + + public Object callStaticMethod(Map transientVars, Class aClass, String s, Object[] objects) throws MethodFailedException { + return null; + } + + public Class classForName(String className, Map context) throws ClassNotFoundException { + Object root = Ognl.getRoot(context); + + try { + if (root instanceof CompoundRoot) { + if (className.startsWith("vs")) { + CompoundRoot compoundRoot = (CompoundRoot) root; + + if ("vs".equals(className)) { + return compoundRoot.peek().getClass(); + } + + int index = Integer.parseInt(className.substring(2)); + + return compoundRoot.get(index - 1).getClass(); + } + } + } catch (Exception e) { + LOG.debug("Got exception when tried to get class for name [{}]", className, e); + } + + return Thread.currentThread().getContextClassLoader().loadClass(className); + } + + private Class[] getArgTypes(Object[] args) { + if (args == null) { + return EMPTY_CLASS_ARRAY; + } + + Class[] classes = new Class[args.length]; + + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + classes[i] = (arg != null) ? arg.getClass() : Object.class; + } + + return classes; + } + + + static class MethodCall { + Class clazz; + String name; + Class[] args; + int hash; + + public MethodCall(Class clazz, String name, Class[] args) { + this.clazz = clazz; + this.name = name; + this.args = args; + this.hash = clazz.hashCode() + name.hashCode(); + + for (Class arg : args) { + hash += arg.hashCode(); + } + } + + @Override + public boolean equals(Object obj) { + MethodCall mc = (CompoundRootAccessor.MethodCall) obj; + + return (mc.clazz.equals(clazz) && mc.name.equals(name) && Arrays.equals(mc.args, args)); + } + + @Override + public int hashCode() { + return hash; + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java new file mode 100644 index 0000000..255a0fc --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectAccessor.java @@ -0,0 +1,29 @@ +/** + * + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.ognl.OgnlValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + +public class ObjectAccessor extends ObjectPropertyAccessor { + @Override + public Object getProperty(Map map, Object o, Object o1) throws OgnlException { + Object obj = super.getProperty(map, o, o1); + + map.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, o.getClass()); + map.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, o1.toString()); + ReflectionContextState.updateCurrentPropertyPath(map, o1); + return obj; + } + + @Override + public void setProperty(Map map, Object o, Object o1, Object o2) throws OgnlException { + super.setProperty(map, o, o1, o2); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java new file mode 100644 index 0000000..714acf7 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/ObjectProxyPropertyAccessor.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.ognl.ObjectProxy; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.OgnlException; +import ognl.OgnlRuntime; +import ognl.PropertyAccessor; +import ognl.OgnlContext; + +import java.util.Map; + +/** + * Is able to access (set/get) properties on a given object. + * <p/> + * Uses Ognl internal. + * + * @author Gabe + */ +public class ObjectProxyPropertyAccessor implements PropertyAccessor { + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceAccessor(OgnlContext context, Object target, Object index) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + /** + * Used by OGNl to generate bytecode + */ + public String getSourceSetter(OgnlContext context, Object target, Object index) { + return null; + } + + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + ObjectProxy proxy = (ObjectProxy) target; + setupContext(context, proxy); + + return OgnlRuntime.getPropertyAccessor(proxy.getValue().getClass()).getProperty(context, target, name); + + } + + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + ObjectProxy proxy = (ObjectProxy) target; + setupContext(context, proxy); + + OgnlRuntime.getPropertyAccessor(proxy.getValue().getClass()).setProperty(context, target, name, value); + } + + /** + * Sets up the context with the last property and last class + * accessed. + * + * @param context + * @param proxy + */ + private void setupContext(Map context, ObjectProxy proxy) { + ReflectionContextState.setLastBeanClassAccessed(context, proxy.getLastClassAccessed()); + ReflectionContextState.setLastBeanPropertyAccessed(context, proxy.getLastPropertyAccessed()); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java new file mode 100644 index 0000000..a1e7536 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkCollectionPropertyAccessor.java @@ -0,0 +1,310 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; +import ognl.OgnlRuntime; +import ognl.SetPropertyAccessor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Gabe + */ +public class XWorkCollectionPropertyAccessor extends SetPropertyAccessor { + + private static final Logger LOG = LogManager.getLogger(XWorkCollectionPropertyAccessor.class); + + public static final String KEY_PROPERTY_FOR_CREATION = "makeNew"; + + //use a basic object Ognl property accessor here + //to access properties of the objects in the Set + //so that nothing is put in the context to screw things up + private ObjectPropertyAccessor _accessor = new ObjectPropertyAccessor(); + + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + private OgnlUtil ognlUtil; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { + this.objectTypeDeterminer = ot; + } + + @Inject + public void setOgnlUtil(OgnlUtil util) { + this.ognlUtil = util; + } + + /** + * Gets the property of a Collection by indexing the collection + * based on a key property. For example, if the key property were + * 'id', this method would convert the key Object to whatever + * type the id property was, and then access the Set like it was + * a Map returning a JavaBean with the value of id property matching + * the input. + * + * @see ognl.PropertyAccessor#getProperty(java.util.Map, Object, Object) + */ + @Override + public Object getProperty(Map context, Object target, Object key) throws OgnlException { + LOG.trace("Entering getProperty()"); + + //check if it is a generic type property. + //if so, return the value from the + //superclass which will determine this. + if (!ReflectionContextState.isGettingByKeyProperty(context) && !key.equals(KEY_PROPERTY_FOR_CREATION)) { + return super.getProperty(context, target, key); + } else { + //reset context property + ReflectionContextState.setGettingByKeyProperty(context,false); + } + Collection c = (Collection) target; + + //get the bean that this collection is a property of + Class lastBeanClass = ReflectionContextState.getLastBeanClassAccessed(context); + + //get the property name that this collection uses + String lastPropertyClass = ReflectionContextState.getLastBeanPropertyAccessed(context); + + //if one or the other is null, assume that it isn't + //set up correctly so just return whatever the + //superclass would + if (lastBeanClass == null || lastPropertyClass == null) { + ReflectionContextState.updateCurrentPropertyPath(context, key); + return super.getProperty(context, target, key); + } + + //get the key property to index the + //collection with from the ObjectTypeDeterminer + String keyProperty = objectTypeDeterminer.getKeyProperty(lastBeanClass, lastPropertyClass); + + //get the collection class of the + Class collClass = objectTypeDeterminer.getElementClass(lastBeanClass, lastPropertyClass, key); + + Class keyType; + Class toGetTypeFrom = (collClass != null) ? collClass : c.iterator().next().getClass(); + try { + keyType = OgnlRuntime.getPropertyDescriptor(toGetTypeFrom, keyProperty).getPropertyType(); + } catch (Exception exc) { + throw new OgnlException("Error getting property descriptor: " + exc.getMessage()); + } + + + if (ReflectionContextState.isCreatingNullObjects(context)) { + Map collMap = getSetMap(context, c, keyProperty); + if (key.toString().equals(KEY_PROPERTY_FOR_CREATION)) { + //this should return the XWorkList + //for this set that contains new entries + //then the ListPropertyAccessor will be called + //to access it in the next sequence + return collMap.get(null); + } + Object realKey = xworkConverter.convertValue(context, key, keyType); + Object value = collMap.get(realKey); + if (value == null + && ReflectionContextState.isCreatingNullObjects(context) + && objectTypeDeterminer + .shouldCreateIfNew(lastBeanClass,lastPropertyClass,c,keyProperty,false)) { + //create a new element and + //set the value of keyProperty + //to be the given value + try { + value=objectFactory.buildBean(collClass, context); + + //set the value of the keyProperty + _accessor.setProperty(context,value,keyProperty,realKey); + + //add the new object to the collection + c.add(value); + + //add to the Map if accessed later + collMap.put(realKey, value); + + + } catch (Exception exc) { + throw new OgnlException("Error adding new element to collection", exc); + } + + } + return value; + } else { + if (key.toString().equals(KEY_PROPERTY_FOR_CREATION)) { + return null; + } + //with getting do iteration + //don't assume for now it is + //optimized to create the Map + //and unlike setting, there is + //no easy key for the Set + Object realKey = xworkConverter.convertValue(context, key, keyType); + return getPropertyThroughIteration(context, c, keyProperty, realKey); + } + } + + /* + * Gets an indexed Map by a given key property with the key being + * the value of the property and the value being the + */ + private Map getSetMap(Map context, Collection collection, String property) throws OgnlException { + LOG.trace("getting set Map"); + + String path = ReflectionContextState.getCurrentPropertyPath(context); + Map map = ReflectionContextState.getSetMap(context, path); + + if (map == null) { + LOG.trace("creating set Map"); + + map = new HashMap(); + map.put(null, new SurrugateList(collection)); + for (Object currTest : collection) { + Object currKey = _accessor.getProperty(context, currTest, property); + if (currKey != null) { + map.put(currKey, currTest); + } + } + ReflectionContextState.setSetMap(context, map, path); + } + return map; + } + + /* + * gets a bean with the given + */ + public Object getPropertyThroughIteration(Map context, Collection collection, String property, Object key) + throws OgnlException { + //TODO + for (Object currTest : collection) { + if (_accessor.getProperty(context, currTest, property).equals(key)) { + return currTest; + } + } + //none found + return null; + } + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + Class convertToClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, name); + + if (name instanceof String && value.getClass().isArray()) { + // looks like the input game in the form of "someCollection.foo" and + // we are expected to define the index values ourselves. + // So let's do it: + + Collection c = (Collection) target; + Object[] values = (Object[]) value; + for (Object v : values) { + try { + Object o = objectFactory.buildBean(convertToClass, context); + ognlUtil.setValue((String) name, context, o, v); + c.add(o); + } catch (Exception e) { + throw new OgnlException("Error converting given String values for Collection.", e); + } + } + + // we don't want to do the normal collection property setting now, since we've already done the work + // just return instead + return; + } + + Object realValue = getRealValue(context, value, convertToClass); + + super.setProperty(context, target, name, realValue); + } + + private Object getRealValue(Map context, Object value, Class convertToClass) { + if (value == null || convertToClass == null) { + return value; + } + return xworkConverter.convertValue(context, value, convertToClass); + } +} + +/** + * @author Gabe + */ +class SurrugateList extends ArrayList { + + private Collection surrugate; + + public SurrugateList(Collection surrugate) { + this.surrugate = surrugate; + } + + @Override + public void add(int arg0, Object arg1) { + if (arg1 != null) { + surrugate.add(arg1); + } + super.add(arg0, arg1); + } + + @Override + public boolean add(Object arg0) { + if (arg0 != null) { + surrugate.add(arg0); + } + return super.add(arg0); + } + + @Override + public boolean addAll(Collection arg0) { + surrugate.addAll(arg0); + return super.addAll(arg0); + } + + @Override + public boolean addAll(int arg0, Collection arg1) { + surrugate.addAll(arg1); + return super.addAll(arg0, arg1); + } + + @Override + public Object set(int arg0, Object arg1) { + if (arg1 != null) { + surrugate.add(arg1); + } + return super.set(arg0, arg1); + } +}
