http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java b/core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java new file mode 100644 index 0000000..7fb7ae2 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Atlassian Software Systems Pty Ltd nor the names of + * its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.opensymphony.xwork2.util.profiling; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +/** + * A timer stack. + * <p/> + * <p/> + * <p/> + * <!-- START SNIPPET: profilingAspect_struts2 --> + * <p/> + * Struts2 profiling aspects involves the following :- + * <ul> + * <li>ActionContextCleanUp</li> + * <li>FreemarkerPageFilter</li> + * <li>DispatcherFilter</li> + * <ul> + * <li>Dispatcher</li> + * <ul> + * <li>creation of DefaultActionProxy</li> + * <ul> + * <li>creation of DefaultActionInvocation</li> + * <ul> + * <li>creation of Action</li> + * </ul> + * </ul> + * <li>execution of DefaultActionProxy</li> + * <ul> + * <li>invocation of DefaultActionInvocation</li> + * <ul> + * <li>invocation of Interceptors</li> + * <li>invocation of Action</li> + * <li>invocation of PreResultListener</li> + * <li>invocation of Result</li> + * </ul> + * </ul> + * </ul> + * </ul> + * </ul> + * <p/> + * <!-- END SNIPPET: profilingAspect_struts2 --> + * <p/> + * <p/> + * <!-- START SNIPPET: profilingAspect_xwork --> + * <p/> + * XWork2 profiling aspects involves the following :- + * <ul> + * <ul> + * <li>creation of DefaultActionProxy</li> + * <ul> + * <li>creation of DefaultActionInvocation</li> + * <ul> + * <li>creation of Action</li> + * </ul> + * </ul> + * <li>execution of DefaultActionProxy</li> + * <ul> + * <li>invocation of DefaultActionInvocation</li> + * <ul> + * <li>invocation of Interceptors</li> + * <li>invocation of Action</li> + * <li>invocation of PreResultListener</li> + * <li>invocation of Result</li> + * </ul> + * </ul> + * </ul> + * </ul> + * <p/> + * <!-- END SNIPPET: profilingAspect_xwork --> + * <p/> + * <p/> + * <!-- START SNIPPET: activationDescription --> + * <p/> + * Activating / Deactivating of the profiling feature could be done through:- + * <p/> + * <!-- END SNIPPET: activationDescription --> + * <p/> + * <p/> + * <p/> + * System properties:- <p/> + * <pre> + * <!-- START SNIPPET: activationThroughSystemProperty --> + * + * -Dxwork.profile.activate=true + * + * <!-- END SNIPPET: activationThroughSystemProperty --> + * </pre> + * <p/> + * <!-- START SNIPPET: activationThroughSystemPropertyDescription --> + * <p/> + * This could be done in the container startup script eg. CATALINA_OPTS in catalina.sh + * (tomcat) or using "java -Dxwork.profile.activate=true -jar start.jar" (jetty) + * <p/> + * <!-- END SNIPPET: activationThroughSystemPropertyDescription --> + * <p/> + * <p/> + * Code :- <p/> + * <pre> + * <!-- START SNIPPET: activationThroughCode --> + * + * UtilTimerStack.setActivate(true); + * + * <!-- END SNIPPET: activationThroughCode --> + * </pre> + * <p/> + * <p/> + * <p/> + * <!-- START SNIPPET: activationThroughCodeDescription --> + * <p/> + * This could be done in a static block, in a Spring bean with lazy-init="false", + * in a Servlet with init-on-startup as some numeric value, in a Filter or + * Listener's init method etc. + * <p/> + * <!-- END SNIPPET: activationThroughCodeDescription --> + * <p/> + * <p/> + * Parameter:- + * <p/> + * <pre> + * <!-- START SNIPPET: activationThroughParameter --> + * + * <action ... > + * ... + * <interceptor-ref name="profiling"> + * <param name="profilingKey">profiling</param> + * </interceptor-ref> + * ... + * </action> + * + * or + * + * <action .... > + * ... + * <interceptor-ref name="profiling" /> + * ... + * </action> + * + * through url + * + * http://host:port/context/namespace/someAction.action?profiling=true + * + * through code + * + * ActionContext.getContext().getParameters().put("profiling", "true); + * + * <!-- END SNIPPET: activationThroughParameter --> + * </pre> + * <p/> + * <p/> + * <!-- START SNIPPET: activationThroughParameterDescription --> + * <p/> + * To use profiling activation through parameter, one will need to pass in through + * the 'profiling' parameter (which is the default) and could be changed through + * the param tag in the interceptor-ref. + * <p/> + * <!-- END SNIPPET: activationThroughParameterDescription --> + * <p/> + * <p/> + * Warning:<p/> + * <!-- START SNIPPET: activationThroughParameterWarning --> + * <p/> + * Profiling activation through a parameter requires the following: + * <p/> + * <ul> + * <li>Profiling interceptor in interceptor stack</li> + * <li>dev mode on (struts.devMode=true in struts.properties) + * </ul> + * <p/> + * <!-- END SNIPPET: activationThroughParameterWarning --> + * <p/> + * <p/> + * <p/> + * <!-- START SNIPPET: filteringDescription --> + * <p/> + * One could filter out the profile logging by having a System property as follows. With this + * 'xwork.profile.mintime' property, one could only log profile information when its execution time + * exceed those specified in 'xwork.profile.mintime' system property. If no such property is specified, + * it will be assumed to be 0, hence all profile information will be logged. + * <p/> + * <!-- END SNIPPET: filteringDescription --> + * <p/> + * <pre> + * <!-- START SNIPPET: filteringCode --> + * + * -Dxwork.profile.mintime=10000 + * + * <!-- END SNIPPET: filteringCode --> + * </pre> + * <p/> + * <!-- START SNIPPET: methodDescription --> + * <p/> + * One could extend the profiling feature provided by Struts2 in their web application as well. + * <p/> + * <!-- END SNIPPET: methodDescription --> + * <p/> + * <pre> + * <!-- START SNIPPET: method1 --> + * + * String logMessage = "Log message"; + * UtilTimerStack.push(logMessage); + * try { + * // do some code + * } + * finally { + * UtilTimerStack.pop(logMessage); // this needs to be the same text as above + * } + * + * <!-- END SNIPPET: method1 --> + * </pre> + * <p/> + * or + * <p/> + * <pre> + * <!-- START SNIPPET: method2 --> + * + * String result = UtilTimerStack.profile("purchaseItem: ", + * new UtilTimerStack.ProfilingBlock<String>() { + * public String doProfiling() { + * // do some code + * return "Ok"; + * } + * }); + * + * <!-- END SNIPPET: method2 --> + * </pre> + * <p/> + * <p/> + * <!-- START SNIPPET: profileLogFile --> + * <p/> + * Profiled result is logged using commons-logging under the logger named + * 'com.opensymphony.xwork2.util.profiling.UtilTimerStack'. Depending on the underlying logging implementation + * say if it is Log4j, one could direct the log to appear in a different file, being emailed to someone or have + * it stored in the db. + * <p/> + * <!-- END SNIPPET: profileLogFile --> + * + * @version $Date$ $Id$ + */ +public class UtilTimerStack { + + // A reference to the current ProfilingTimerBean + protected static ThreadLocal<ProfilingTimerBean> current = new ThreadLocal<>(); + + /** + * System property that controls whether this timer should be used or not. Set to "true" activates + * the timer. Set to "false" to disactivate. + */ + public static final String ACTIVATE_PROPERTY = "xwork.profile.activate"; + + /** + * System property that controls the min time, that if exceeded will cause a log (at INFO level) to be + * created. + */ + public static final String MIN_TIME = "xwork.profile.mintime"; + + private static final Logger LOG = LogManager.getLogger(UtilTimerStack.class); + + /** + * Initialized in a static block, it can be changed at runtime by calling setActive(...) + */ + private static boolean active; + + static { + active = "true".equalsIgnoreCase(System.getProperty(ACTIVATE_PROPERTY)); + } + + /** + * Create and start a performance profiling with the <code>name</code> given. Deal with + * profile hierarchy automatically, so caller don't have to be concern about it. + * + * @param name profile name + */ + public static void push(String name) { + if (!isActive()) { + return; + } + + //create a new timer and start it + ProfilingTimerBean newTimer = new ProfilingTimerBean(name); + newTimer.setStartTime(); + + //if there is a current timer - add the new timer as a child of it + ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get(); + if (currentTimer != null) { + currentTimer.addChild(newTimer); + } + + //set the new timer to be the current timer + current.set(newTimer); + } + + /** + * End a preformance profiling with the <code>name</code> given. Deal with + * profile hierarchy automatically, so caller don't have to be concern about it. + * + * @param name profile name + */ + public static void pop(String name) { + if (!isActive()) { + return; + } + + ProfilingTimerBean currentTimer = current.get(); + + //if the timers are matched up with each other (ie push("a"); pop("a")); + if (currentTimer != null && name != null && name.equals(currentTimer.getResource())) { + currentTimer.setEndTime(); + ProfilingTimerBean parent = currentTimer.getParent(); + //if we are the root timer, then print out the times + if (parent == null) { + printTimes(currentTimer); + current.set(null); //for those servers that use thread pooling + } else { + current.set(parent); + } + } else { + //if timers are not matched up, then print what we have, and then print warning. + if (currentTimer != null) { + printTimes(currentTimer); + current.set(null); //prevent printing multiple times + LOG.warn("Unmatched Timer. Was expecting {}, instead got {}", currentTimer.getResource(), name); + } + } + } + + /** + * Do a log (at INFO level) of the time taken for this particular profiling. + * + * @param currentTimer profiling timer bean + */ + private static void printTimes(ProfilingTimerBean currentTimer) { + LOG.info(currentTimer.getPrintable(getMinTime())); + } + + /** + * Get the min time for this profiling, it searches for a System property + * 'xwork.profile.mintime' and default to 0. + * + * @return long + */ + private static long getMinTime() { + try { + return Long.parseLong(System.getProperty(MIN_TIME, "0")); + } catch (NumberFormatException e) { + return -1; + } + } + + /** + * Determine if profiling is being activated, by searching for a system property + * 'xwork.profile.activate', default to false (profiling is off). + * + * @return <tt>true</tt>, if active, <tt>false</tt> otherwise. + */ + public static boolean isActive() { + return active; + } + + /** + * Turn profiling on or off. + * + * @param active + */ + public static void setActive(boolean active) { + if (active) { + System.setProperty(ACTIVATE_PROPERTY, "true"); + } else { + System.clearProperty(ACTIVATE_PROPERTY); + } + UtilTimerStack.active = active; + } + + + /** + * A convenience method that allows <code>block</code> of code subjected to profiling to be executed + * and avoid the need of coding boiler code that does pushing (UtilTimeBean.push(...)) and + * poping (UtilTimerBean.pop(...)) in a try ... finally ... block. + * <p/> + * <p/> + * <p/> + * Example of usage: + * <pre> + * // we need a returning result + * String result = UtilTimerStack.profile("purchaseItem: ", + * new UtilTimerStack.ProfilingBlock<String>() { + * public String doProfiling() { + * getMyService().purchaseItem(....) + * return "Ok"; + * } + * }); + * </pre> + * or + * <pre> + * // we don't need a returning result + * UtilTimerStack.profile("purchaseItem: ", + * new UtilTimerStack.ProfilingBlock<String>() { + * public String doProfiling() { + * getMyService().purchaseItem(....) + * return null; + * } + * }); + * </pre> + * + * @param <T> any return value if there's one. + * @param name profile name + * @param block code block subjected to profiling + * @return T + * @throws Exception + */ + public static <T> T profile(String name, ProfilingBlock<T> block) throws Exception { + UtilTimerStack.push(name); + try { + return block.doProfiling(); + } finally { + UtilTimerStack.pop(name); + } + } + + /** + * A callback interface where code subjected to profile is to be executed. This eliminates the need + * of coding boiler code that does pushing (UtilTimerBean.push(...)) and poping (UtilTimerBean.pop(...)) + * in a try ... finally ... block. + * + * @param <T> + * @version $Date$ $Id$ + */ + public static interface ProfilingBlock<T> { + + /** + * Method that execute the code subjected to profiling. + * + * @return profiles Type + * @throws Exception + */ + T doProfiling() throws Exception; + } +}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html b/core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html new file mode 100644 index 0000000..c0b1f7f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/profiling/package.html @@ -0,0 +1 @@ +<body>Classes to enable profiling of action execution.</body> http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java new file mode 100644 index 0000000..704b5af --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextFactory.java @@ -0,0 +1,15 @@ +package com.opensymphony.xwork2.util.reflection; + +import java.util.Map; + +public interface ReflectionContextFactory { + /** + * Creates and returns a new standard naming context for evaluating an OGNL + * expression. + * + * @param root the root of the object graph + * @return a new Map with the keys <CODE>root</CODE> and <CODE>context</CODE> + * set appropriately + */ + Map createDefaultContext( Object root ); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java new file mode 100644 index 0000000..9de464b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionContextState.java @@ -0,0 +1,173 @@ +/* + * 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.util.reflection; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; + +import java.util.HashMap; +import java.util.Map; + +/** + * Manages variables in the reflection context and returns values + * to be used by the application. + * + * @author Gabe + */ +public class ReflectionContextState { + + private static final String GETTING_BY_KEY_PROPERTY = "xwork.getting.by.key.property"; + private static final String SET_MAP_KEY = "set.map.key"; + + public static final String CURRENT_PROPERTY_PATH="current.property.path"; + public static final String FULL_PROPERTY_PATH="current.property.path"; + public static final String CREATE_NULL_OBJECTS = "xwork.NullHandler.createNullObjects"; + public static final String DENY_METHOD_EXECUTION = "xwork.MethodAccessor.denyMethodExecution"; + public static final String DENY_INDEXED_ACCESS_EXECUTION = "xwork.IndexedPropertyAccessor.denyMethodExecution"; + + public static boolean isCreatingNullObjects(Map<String, Object> context) { + //TODO + return getBooleanProperty(ReflectionContextState.CREATE_NULL_OBJECTS, context); + } + + public static void setCreatingNullObjects(Map<String, Object> context, boolean creatingNullObjects) { + setBooleanValue(ReflectionContextState.CREATE_NULL_OBJECTS, context, creatingNullObjects); + } + + public static boolean isGettingByKeyProperty(Map<String, Object> context) { + return getBooleanProperty(GETTING_BY_KEY_PROPERTY, context); + } + + public static void setDenyMethodExecution(Map<String, Object> context, boolean denyMethodExecution) { + setBooleanValue(ReflectionContextState.DENY_METHOD_EXECUTION, context, denyMethodExecution); + } + + public static boolean isDenyMethodExecution(Map<String, Object> context) { + return getBooleanProperty(ReflectionContextState.DENY_METHOD_EXECUTION, context); + } + + public static void setGettingByKeyProperty(Map<String, Object> context, boolean gettingByKeyProperty) { + setBooleanValue(GETTING_BY_KEY_PROPERTY, context, gettingByKeyProperty); + } + + public static boolean isReportingConversionErrors(Map<String, Object> context) { + return getBooleanProperty(XWorkConverter.REPORT_CONVERSION_ERRORS, context); + } + + public static void setReportingConversionErrors(Map<String, Object> context, boolean reportingErrors) { + setBooleanValue(XWorkConverter.REPORT_CONVERSION_ERRORS, context, reportingErrors); + } + + public static Class getLastBeanClassAccessed(Map<String, Object> context) { + return (Class)context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + } + + public static void setLastBeanPropertyAccessed(Map<String, Object> context, String property) { + context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, property); + } + + public static String getLastBeanPropertyAccessed(Map<String, Object> context) { + return (String)context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + } + + public static void setLastBeanClassAccessed(Map<String, Object> context, Class clazz) { + context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, clazz); + } + /** + * Gets the current property path but not completely. + * It does not use the [ and ] used in some representations + * of Maps and Lists. The reason for this is that the current + * property path is only currently used for caching purposes + * so there is no real reason to have an exact replica. + * + * <p/>So if the real path is myProp.myMap['myKey'] this would + * return myProp.myMap.myKey. + * + * @param context + */ + public static String getCurrentPropertyPath(Map<String, Object> context) { + return (String)context.get(CURRENT_PROPERTY_PATH); + } + + public static String getFullPropertyPath(Map<String, Object> context) { + return (String)context.get(FULL_PROPERTY_PATH); + } + + public static void setFullPropertyPath(Map<String, Object> context, String path) { + context.put(FULL_PROPERTY_PATH, path); + + } + + public static void updateCurrentPropertyPath(Map<String, Object> context, Object name) { + String currentPath=getCurrentPropertyPath(context); + if (name!=null) { + if (currentPath!=null) { + StringBuilder sb = new StringBuilder(currentPath); + sb.append("."); + sb.append(name.toString()); + currentPath = sb.toString(); + } else { + currentPath = name.toString(); + } + context.put(CURRENT_PROPERTY_PATH, currentPath); + } + } + + public static void setSetMap(Map<String, Object> context, Map<Object, Object> setMap, String path) { + Map<Object, Map<Object, Object>> mapOfSetMaps=(Map)context.get(SET_MAP_KEY); + if (mapOfSetMaps==null) { + mapOfSetMaps = new HashMap<>(); + context.put(SET_MAP_KEY, mapOfSetMaps); + } + mapOfSetMaps.put(path, setMap); + } + + public static Map<Object, Object> getSetMap(Map<String, Object> context, String path) { + Map<Object, Map<Object, Object>> mapOfSetMaps=(Map)context.get(SET_MAP_KEY); + if (mapOfSetMaps==null) { + return null; + } + return mapOfSetMaps.get(path); + } + + private static boolean getBooleanProperty(String property, Map<String, Object> context) { + Boolean myBool=(Boolean)context.get(property); + return (myBool==null)?false:myBool.booleanValue(); + } + + private static void setBooleanValue(String property, Map<String, Object> context, boolean value) { + context.put(property, new Boolean(value)); + } + + /** + * + */ + public static void clearCurrentPropertyPath(Map<String, Object> context) { + context.put(CURRENT_PROPERTY_PATH, null); + + } + + public static void clear(Map<String, Object> context) { + if (context != null) { + context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED,null); + context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED,null); + + context.put(CURRENT_PROPERTY_PATH,null); + context.put(FULL_PROPERTY_PATH,null); + } + + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java new file mode 100644 index 0000000..a6d107d --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionException.java @@ -0,0 +1,41 @@ +package com.opensymphony.xwork2.util.reflection; + +import com.opensymphony.xwork2.XWorkException; + +public class ReflectionException extends XWorkException { + + public ReflectionException() { + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s) { + super(s); + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s, Object target) { + super(s, target); + // TODO Auto-generated constructor stub + } + + public ReflectionException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + public ReflectionException(Throwable cause, Object target) { + super(cause, target); + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s, Throwable cause) { + super(s, cause); + // TODO Auto-generated constructor stub + } + + public ReflectionException(String s, Throwable cause, Object target) { + super(s, cause, target); + // TODO Auto-generated constructor stub + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java new file mode 100644 index 0000000..b5c7d8a --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionExceptionHandler.java @@ -0,0 +1,14 @@ +package com.opensymphony.xwork2.util.reflection; + +/** + * Declares a class that wants to handle its own reflection exceptions + */ +public interface ReflectionExceptionHandler { + + /** + * Handles a reflection exception + * + * @param ex The reflection exception + */ + void handle(ReflectionException ex); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java new file mode 100644 index 0000000..c48292f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProvider.java @@ -0,0 +1,141 @@ +package com.opensymphony.xwork2.util.reflection; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +public interface ReflectionProvider { + + Method getGetMethod(Class targetClass, String propertyName) throws IntrospectionException, ReflectionException; + + Method getSetMethod(Class targetClass, String propertyName) throws IntrospectionException, ReflectionException; + + Field getField(Class inClass, String name); + + /** + * 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 + */ + void setProperties(Map<String, ?> props, Object o, Map<String, Object> context); + + /** + * 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 + */ + void setProperties(Map<String, ?> props, Object o, Map<String, Object> context, boolean throwPropertyExceptions) throws ReflectionException; + + /** + * Sets the properties on the object using the default context, defaulting to not throwing + * exceptions for problems setting the properties. + * + * @param properties + * @param o + */ + void setProperties(Map<String, ?> properties, Object o); + + /** + * This method returns a PropertyDescriptor for the given class and property name using + * a Map lookup (using getPropertyDescriptorsMap()). + */ + PropertyDescriptor getPropertyDescriptor(Class targetClass, String propertyName) throws IntrospectionException, ReflectionException; + + /** + * 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. + */ + void copy(Object from, Object to, Map<String, Object> context, Collection<String> exclusions, Collection<String> inclusions); + + /** + * 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 + */ + Object getRealTarget(String property, Map<String, Object> context, Object root) throws ReflectionException; + + /** + * 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 properties + */ + void setProperty(String name, Object value, Object o, Map<String, Object> context, boolean 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 + */ + void setProperty(String name, Object value, Object o, Map<String, Object> context); + + /** + * 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. + */ + Map<String, Object> getBeanMap(Object source) throws IntrospectionException, ReflectionException; + + /** + * Evaluates the given OGNL expression to extract a value from the given root + * object in a given context + * + * @param expression the OGNL expression to be parsed + * @param context the naming context for the evaluation + * @param root the root object for the OGNL expression + * @return the result of evaluating the expression + */ + Object getValue( String expression, Map<String, Object> context, Object root ) throws ReflectionException; + + /** + * Evaluates the given OGNL expression to insert a value into the object graph + * rooted at the given root object given the context. + * + * @param expression the OGNL expression to be parsed + * @param root the root object for the OGNL expression + * @param context the naming context for the evaluation + * @param value the value to insert into the object graph + */ + void setValue( String expression, Map<String, Object> context, Object root, Object value ) throws ReflectionException; + + /** + * 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. + */ + PropertyDescriptor[] getPropertyDescriptors(Object source) throws IntrospectionException; +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java new file mode 100644 index 0000000..a05b7d6 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/reflection/ReflectionProviderFactory.java @@ -0,0 +1,10 @@ +package com.opensymphony.xwork2.util.reflection; + +import com.opensymphony.xwork2.ActionContext; + +public class ReflectionProviderFactory { + + public static ReflectionProvider getInstance() { + return ActionContext.getContext().getContainer().getInstance(ReflectionProvider.class); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java b/core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java new file mode 100644 index 0000000..6650817 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/validator/ActionValidatorManager.java @@ -0,0 +1,87 @@ +/* + * 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.validator; + +import java.util.List; + +/** + * ActionValidatorManager is the main interface for validation managers (regular and annotation based). + * + * @author Rainer Hermanns + */ +public interface ActionValidatorManager { + + /** + * Returns a list of validators for the given class, context, and method. This is the primary + * lookup method for validators. + * + * @param clazz the class to lookup. + * @param context the context of the action class - can be <tt>null</tt>. + * @param method the name of the method being invoked on the action - can be <tt>null</tt>. + * @return a list of all validators for the given class and context. + */ + List<Validator> getValidators(Class clazz, String context, String method); + + /** + * Returns a list of validators for the given class and context. This is the primary + * lookup method for validators. + * + * @param clazz the class to lookup. + * @param context the context of the action class - can be <tt>null</tt>. + * @return a list of all validators for the given class and context. + */ + List<Validator> getValidators(Class clazz, String context); + + /** + * Validates the given object using action and its context. + * + * @param object the action to validate. + * @param context the action's context. + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context) throws ValidationException; + + /** + * Validates an action give its context and a validation context. + * + * @param object the action to validate. + * @param context the action's context. + * @param validatorContext the validation context to use + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException; + + /** + * Validates the given object using an action, its context, and the name of the method being invoked on the action. + * + * @param object the action to validate. + * @param context the action's context. + * @param method the name of the method being invoked on the action - can be <tt>null</tt>. + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context, String method) throws ValidationException; + + /** + * Validates an action give its context and a validation context. + * + * @param object the action to validate. + * @param context the action's context. + * @param validatorContext the validation context to use + * @param method the name of the method being invoked on the action - can be <tt>null</tt>. + * @throws ValidationException if an error happens when validating the action. + */ + void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException; +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java b/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java new file mode 100644 index 0000000..e98f8ee --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java @@ -0,0 +1,380 @@ +/* + * 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.validator; + + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; + +/** + * AnnotationActionValidatorManager is the entry point into XWork's annotations-based validator framework. + * Validation rules are specified as annotations within the source files. + * + * @author Rainer Hermanns + * @author jepjep + */ +public class AnnotationActionValidatorManager implements ActionValidatorManager { + + /** + * The file suffix for any validation file. + */ + protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml"; + + private final Map<String, List<ValidatorConfig>> validatorCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>()); + private final Map<String, List<ValidatorConfig>> validatorFileCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>()); + private static final Logger LOG = LogManager.getLogger(AnnotationActionValidatorManager.class); + + private ValidatorFactory validatorFactory; + private ValidatorFileParser validatorFileParser; + private FileManager fileManager; + private boolean reloadingConfigs; + + @Inject + public void setValidatorFactory(ValidatorFactory fac) { + this.validatorFactory = fac; + } + + @Inject + public void setValidatorFileParser(ValidatorFileParser parser) { + this.validatorFileParser = parser; + } + + @Inject + public void setFileManagerFactory(FileManagerFactory fileManagerFactory) { + this.fileManager = fileManagerFactory.getFileManager(); + } + + @Inject(value = XWorkConstants.RELOAD_XML_CONFIGURATION, required = false) + public void setReloadingConfigs(String reloadingConfigs) { + this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs); + } + + public List<Validator> getValidators(Class clazz, String context) { + return getValidators(clazz, context, null); + } + + public List<Validator> getValidators(Class clazz, String context, String method) { + final String validatorKey = buildValidatorKey(clazz, context); + final List<ValidatorConfig> cfgs; + + if (validatorCache.containsKey(validatorKey)) { + if (reloadingConfigs) { + validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null)); + } + } else { + validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null)); + } + + // get the set of validator configs + cfgs = new ArrayList<ValidatorConfig>(validatorCache.get(validatorKey)); + + ValueStack stack = ActionContext.getContext().getValueStack(); + + // create clean instances of the validators for the caller's use + ArrayList<Validator> validators = new ArrayList<>(cfgs.size()); + for (ValidatorConfig cfg : cfgs) { + if (method == null || method.equals(cfg.getParams().get("methodName"))) { + Validator validator = validatorFactory.getValidator( + new ValidatorConfig.Builder(cfg) + .removeParam("methodName") + .build()); + validator.setValidatorType(cfg.getType()); + validator.setValueStack(stack); + validators.add(validator); + } + } + + return validators; + } + + public void validate(Object object, String context) throws ValidationException { + validate(object, context, (String) null); + } + + public void validate(Object object, String context, String method) throws ValidationException { + ValidatorContext validatorContext = new DelegatingValidatorContext(object); + validate(object, context, validatorContext, method); + } + + public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException { + validate(object, context, validatorContext, null); + } + + public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException { + List<Validator> validators = getValidators(object.getClass(), context, method); + Set<String> shortcircuitedFields = null; + + for (final Validator validator : validators) { + try { + validator.setValidatorContext(validatorContext); + + LOG.debug("Running validator: {} for object {} and method {}", validator, object, method); + + FieldValidator fValidator = null; + String fullFieldName = null; + + if (validator instanceof FieldValidator) { + fValidator = (FieldValidator) validator; + fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName()); + + if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) { + LOG.debug("Short-circuited, skipping"); + continue; + } + } + + if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) { + // get number of existing errors + List<String> errs = null; + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName); + + if (fieldErrors != null) { + errs = new ArrayList<>(fieldErrors); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection<String> actionErrors = validatorContext.getActionErrors(); + + if (actionErrors != null) { + errs = new ArrayList<>(actionErrors); + } + } + + validator.validate(object); + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName); + + if ((errCol != null) && !errCol.equals(errs)) { + LOG.debug("Short-circuiting on field validation"); + + if (shortcircuitedFields == null) { + shortcircuitedFields = new TreeSet<String>(); + } + + shortcircuitedFields.add(fullFieldName); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection<String> errCol = validatorContext.getActionErrors(); + + if ((errCol != null) && !errCol.equals(errs)) { + LOG.debug("Short-circuiting"); + break; + } + } + + continue; + } + + validator.validate(object); + } finally { + validator.setValidatorContext(null); + } + + } + } + + /** + * Builds a key for validators - used when caching validators. + * + * @param clazz the action. + * @return a validator key which is the class name plus context. + */ + protected static String buildValidatorKey(Class clazz, String context) { + ActionInvocation invocation = ActionContext.getContext().getActionInvocation(); + ActionProxy proxy = invocation.getProxy(); + ActionConfig config = proxy.getConfig(); + + StringBuilder sb = new StringBuilder(clazz.getName()); + sb.append("/"); + if (StringUtils.isNotBlank(config.getPackageName())) { + sb.append(config.getPackageName()); + sb.append("/"); + } + + // the key needs to use the name of the action from the config file, + // instead of the url, so wild card actions will have the same validator + // see WW-2996 + + // UPDATE: + // WW-3753 Using the config name instead of the context only for + // wild card actions to keep the flexibility provided + // by the original design (such as mapping different contexts + // to the same action and method if desired) + String configName = config.getName(); + if (configName.contains(ActionConfig.WILDCARD)) { + sb.append(configName); + sb.append("|"); + sb.append(proxy.getMethod()); + } else { + sb.append(context); + } + + return sb.toString(); + } + + private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) { + String fileName = aClass.getName().replace('.', '/') + "-" + context.replace('/', '-') + VALIDATION_CONFIG_SUFFIX; + + return loadFile(fileName, aClass, checkFile); + } + + + protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) { + + String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX; + + List<ValidatorConfig> result = new ArrayList<>(loadFile(fileName, aClass, checkFile)); + + AnnotationValidationConfigurationBuilder builder = new AnnotationValidationConfigurationBuilder(validatorFactory); + + List<ValidatorConfig> annotationResult = new ArrayList<>(builder.buildAnnotationClassValidatorConfigs(aClass)); + + result.addAll(annotationResult); + + return result; + + } + + /** + * <p>This method 'collects' all the validator configurations for a given + * action invocation.</p> + * <p/> + * <p>It will traverse up the class hierarchy looking for validators for every super class + * and directly implemented interface of the current action, as well as adding validators for + * any alias of this invocation. Nifty!</p> + * <p/> + * <p>Given the following class structure: + * <pre> + * interface Thing; + * interface Animal extends Thing; + * interface Quadraped extends Animal; + * class AnimalImpl implements Animal; + * class QuadrapedImpl extends AnimalImpl implements Quadraped; + * class Dog extends QuadrapedImpl; + * </pre></p> + * <p/> + * <p>This method will look for the following config files for Dog: + * <pre> + * Animal + * Animal-context + * AnimalImpl + * AnimalImpl-context + * Quadraped + * Quadraped-context + * QuadrapedImpl + * QuadrapedImpl-context + * Dog + * Dog-context + * </pre></p> + * <p/> + * <p>Note that the validation rules for Thing is never looked for because no class in the + * hierarchy directly implements Thing.</p> + * + * @param clazz the Class to look up validators for. + * @param context the context to use when looking up validators. + * @param checkFile true if the validation config file should be checked to see if it has been + * updated. + * @param checked the set of previously checked class-contexts, null if none have been checked + * @return a list of validator configs for the given class and context. + */ + private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) { + List<ValidatorConfig> validatorConfigs = new ArrayList<>(); + + if (checked == null) { + checked = new TreeSet<>(); + } else if (checked.contains(clazz.getName())) { + return validatorConfigs; + } + + if (clazz.isInterface()) { + Class[] interfaces = clazz.getInterfaces(); + + for (Class anInterface : interfaces) { + validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked)); + } + } else { + if (!clazz.equals(Object.class)) { + validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked)); + } + } + + // look for validators for implemented interfaces + Class[] interfaces = clazz.getInterfaces(); + + for (Class anInterface1 : interfaces) { + if (checked.contains(anInterface1.getName())) { + continue; + } + + validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile)); + + if (context != null) { + validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile)); + } + + checked.add(anInterface1.getName()); + } + + validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile)); + + if (context != null) { + validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile)); + } + + checked.add(clazz.getName()); + + return validatorConfigs; + } + + private List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) { + List<ValidatorConfig> retList = Collections.emptyList(); + + URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz); + + if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) { + try (InputStream is = fileManager.loadFile(fileUrl)) { + if (is != null) { + retList = new ArrayList<>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName)); + } + } catch (IOException e) { + LOG.error("Caught exception while loading file {}", fileName, e); + } + + validatorFileCache.put(fileName, retList); + } else { + retList = validatorFileCache.get(fileName); + } + + return retList; + } +}
