Return-Path:
X-Original-To: apmail-struts-commits-archive@minotaur.apache.org
Delivered-To: apmail-struts-commits-archive@minotaur.apache.org
Received: from mail.apache.org (hermes.apache.org [140.211.11.3])
by minotaur.apache.org (Postfix) with SMTP id 4979F18F28
for ;
Wed, 17 Jun 2015 21:09:21 +0000 (UTC)
Received: (qmail 77467 invoked by uid 500); 17 Jun 2015 21:09:04 -0000
Delivered-To: apmail-struts-commits-archive@struts.apache.org
Received: (qmail 77370 invoked by uid 500); 17 Jun 2015 21:09:04 -0000
Mailing-List: contact commits-help@struts.apache.org; run by ezmlm
Precedence: bulk
List-Help:
List-Unsubscribe:
List-Post:
List-Id:
Reply-To: dev@struts.apache.org
Delivered-To: mailing list commits@struts.apache.org
Received: (qmail 75322 invoked by uid 99); 17 Jun 2015 21:09:02 -0000
Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org)
(140.211.11.23)
by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 17 Jun 2015 21:09:02 +0000
Received: by git1-us-west.apache.org (ASF Mail Server at
git1-us-west.apache.org, from userid 33)
id A2A66E3C3C; Wed, 17 Jun 2015 21:09:02 +0000 (UTC)
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
From: lukaszlenart@apache.org
To: commits@struts.apache.org
Date: Wed, 17 Jun 2015 21:09:41 -0000
Message-Id: <5a92fe33954d414a93f577cf6a3ab0d0@git.apache.org>
In-Reply-To:
References:
X-Mailer: ASF-Git Admin Mailer
Subject: [41/57] [partial] struts git commit: Merges xwork packages into
struts
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java
new file mode 100644
index 0000000..4d8eebb
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceMap.java
@@ -0,0 +1,605 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ *
+ * 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.inject.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.ref.Reference;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.opensymphony.xwork2.inject.util.ReferenceType.STRONG;
+
+/**
+ * Concurrent hash map that wraps keys and/or values in soft or weak
+ * references. Does not support null keys or values. Uses identity equality
+ * for weak and soft keys.
+ *
+ *
The concurrent semantics of {@link ConcurrentHashMap} combined with the
+ * fact that the garbage collector can asynchronously reclaim and clean up
+ * after keys and values at any time can lead to some racy semantics. For
+ * example, {@link #size()} returns an upper bound on the size, i.e. the actual
+ * size may be smaller in cases where the key or value has been reclaimed but
+ * the map entry has not been cleaned up yet.
+ *
+ *
Another example: If {@link #get(Object)} cannot find an existing entry
+ * for a key, it will try to create one. This operation is not atomic. One
+ * thread could {@link #put(Object, Object)} a value between the time another
+ * thread running {@code get()} checks for an entry and decides to create one.
+ * In this case, the newly created value will replace the put value in the
+ * map. Also, two threads running {@code get()} concurrently can potentially
+ * create duplicate values for a given key.
+ *
+ *
In other words, this class is great for caching but not atomicity.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@SuppressWarnings("unchecked")
+public class ReferenceMap implements Map, Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ transient ConcurrentMap
+ * Note: By default actionErrors and actionMessages are excluded when copping object's properties.
+ *
+ *
+ * Interceptor parameters:
+ *
+ *
+ *
excludes (optional) - the list of parameter names to exclude from copying (all others will be included).
+ *
includes (optional) - the list of parameter names to include when copying (all others will be excluded).
+ *
+ *
+ * Extending the interceptor:
+ *
+ *
+ * There are no known extension points to this interceptor.
+ *
+ *
+ * @author mrdon
+ * @author tm_jee ( tm_jee(at)yahoo.co.uk )
+ * @see com.opensymphony.xwork2.ActionChainResult
+ */
+public class ChainingInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ChainingInterceptor.class);
+
+ private static final String ACTION_ERRORS = "actionErrors";
+ private static final String FIELD_ERRORS = "fieldErrors";
+ private static final String ACTION_MESSAGES = "actionMessages";
+
+ private boolean copyMessages = false;
+ private boolean copyErrors = false;
+ private boolean copyFieldErrors = false;
+
+ protected Collection excludes;
+
+ protected Collection includes;
+ protected ReflectionProvider reflectionProvider;
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider prov) {
+ this.reflectionProvider = prov;
+ }
+
+ @Inject(value = "struts.xwork.chaining.copyErrors", required = false)
+ public void setCopyErrors(String copyErrors) {
+ this.copyErrors = "true".equalsIgnoreCase(copyErrors);
+ }
+
+ @Inject(value = "struts.xwork.chaining.copyFieldErrors", required = false)
+ public void setCopyFieldErrors(String copyFieldErrors) {
+ this.copyFieldErrors = "true".equalsIgnoreCase(copyFieldErrors);
+ }
+
+ @Inject(value = "struts.xwork.chaining.copyMessages", required = false)
+ public void setCopyMessages(String copyMessages) {
+ this.copyMessages = "true".equalsIgnoreCase(copyMessages);
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ ValueStack stack = invocation.getStack();
+ CompoundRoot root = stack.getRoot();
+ if (shouldCopyStack(invocation, root)) {
+ copyStack(invocation, root);
+ }
+ return invocation.invoke();
+ }
+
+ private void copyStack(ActionInvocation invocation, CompoundRoot root) {
+ List list = prepareList(root);
+ Map ctxMap = invocation.getInvocationContext().getContextMap();
+ for (Object object : list) {
+ if (shouldCopy(object)) {
+ reflectionProvider.copy(object, invocation.getAction(), ctxMap, prepareExcludes(), includes);
+ }
+ }
+ }
+
+ private Collection prepareExcludes() {
+ Collection localExcludes = excludes;
+ if (!copyErrors || !copyMessages ||!copyFieldErrors) {
+ if (localExcludes == null) {
+ localExcludes = new HashSet();
+ if (!copyErrors) {
+ localExcludes.add(ACTION_ERRORS);
+ }
+ if (!copyMessages) {
+ localExcludes.add(ACTION_MESSAGES);
+ }
+ if (!copyFieldErrors) {
+ localExcludes.add(FIELD_ERRORS);
+ }
+ }
+ }
+ return localExcludes;
+ }
+
+ private boolean shouldCopy(Object o) {
+ return o != null && !(o instanceof Unchainable);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List prepareList(CompoundRoot root) {
+ List list = new ArrayList(root);
+ list.remove(0);
+ Collections.reverse(list);
+ return list;
+ }
+
+ private boolean shouldCopyStack(ActionInvocation invocation, CompoundRoot root) throws Exception {
+ Result result = invocation.getResult();
+ return root.size() > 1 && (result == null || ActionChainResult.class.isAssignableFrom(result.getClass()));
+ }
+
+ /**
+ * Gets list of parameter names to exclude
+ *
+ * @return the exclude list
+ */
+ public Collection getExcludes() {
+ return excludes;
+ }
+
+ /**
+ * Sets the list of parameter names to exclude from copying (all others will be included).
+ *
+ * @param excludes the excludes list
+ */
+ public void setExcludes(Collection excludes) {
+ this.excludes = excludes;
+ }
+
+ /**
+ * Gets list of parameter names to include
+ *
+ * @return the include list
+ */
+ public Collection getIncludes() {
+ return includes;
+ }
+
+ /**
+ * Sets the list of parameter names to include when copying (all others will be excluded).
+ *
+ * @param includes the includes list
+ */
+ public void setIncludes(Collection includes) {
+ this.includes = includes;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java
new file mode 100644
index 0000000..47d020a
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java
@@ -0,0 +1,144 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ValidationAware;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.commons.lang3.StringEscapeUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ *
+ * ConversionErrorInterceptor adds conversion errors from the ActionContext to the Action's field errors.
+ *
+ *
+ * This interceptor adds any error found in the {@link ActionContext}'s conversionErrors map as a field error (provided
+ * that the action implements {@link ValidationAware}). In addition, any field that contains a validation error has its
+ * original value saved such that any subsequent requests for that value return the original value rather than the value
+ * in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to
+ * display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to
+ * the user).
+ *
+ *
+ *
+ *
+ * Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
None
+ *
+ *
+ *
+ *
+ *
+ * Extending the interceptor:
+ *
+ *
+ *
+ *
+ *
+ * Because this interceptor is not web-specific, it abstracts the logic for whether an error should be added. This
+ * allows for web-specific interceptors to use more complex logic in the {@link #shouldAddError} method for when a value
+ * has a conversion error but is null or empty or otherwise indicates that the value was never actually entered by the
+ * user.
+ *
+ *
+ *
+ * Example code:
+ *
+ *
+ *
+ * @author Jason Carreira
+ */
+public class ConversionErrorInterceptor extends AbstractInterceptor {
+
+ public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
+
+ protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
+ return escape(value);
+ }
+
+ protected String escape(Object value) {
+ return "\"" + StringEscapeUtils.escapeJava(String.valueOf(value)) + "\"";
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+
+ ActionContext invocationContext = invocation.getInvocationContext();
+ Map conversionErrors = invocationContext.getConversionErrors();
+ ValueStack stack = invocationContext.getValueStack();
+
+ HashMap fakie = null;
+
+ for (Map.Entry entry : conversionErrors.entrySet()) {
+ String propertyName = entry.getKey();
+ Object value = entry.getValue();
+
+ if (shouldAddError(propertyName, value)) {
+ String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
+
+ Object action = invocation.getAction();
+ if (action instanceof ValidationAware) {
+ ValidationAware va = (ValidationAware) action;
+ va.addFieldError(propertyName, message);
+ }
+
+ if (fakie == null) {
+ fakie = new HashMap<>();
+ }
+
+ fakie.put(propertyName, getOverrideExpr(invocation, value));
+ }
+ }
+
+ if (fakie != null) {
+ // if there were some errors, put the original (fake) values in place right before the result
+ stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
+ invocation.addPreResultListener(new PreResultListener() {
+ public void beforeResult(ActionInvocation invocation, String resultCode) {
+ Map fakie = (Map) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
+
+ if (fakie != null) {
+ invocation.getStack().setExprOverrides(fakie);
+ }
+ }
+ });
+ }
+ return invocation.invoke();
+ }
+
+ protected boolean shouldAddError(String propertyName, Object value) {
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
new file mode 100644
index 0000000..13cea0e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
@@ -0,0 +1,214 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ValidationAware;
+import com.opensymphony.xwork2.interceptor.annotations.InputConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ *
+ * An interceptor that makes sure there are not validation errors before allowing the interceptor chain to continue.
+ * This interceptor does not perform any validation.
+ *
+ * This interceptor does nothing if the name of the method being invoked is specified in the excludeMethods
+ * parameter. excludeMethods accepts a comma-delimited list of method names. For example, requests to
+ * foo!input.action and foo!back.action will be skipped by this interceptor if you set the
+ * excludeMethods parameter to "input, back".
+ *
+ * Note: As this method extends off MethodFilterInterceptor, it is capable of
+ * deciding if it is applicable only to selective methods in the action class. This is done by adding param tags
+ * for the interceptor element, naming either a list of excluded method names and/or a list of included method
+ * names, whereby includeMethods overrides excludedMethods. A single * sign is interpreted as wildcard matching
+ * all methods for both parameters.
+ * See {@link MethodFilterInterceptor} for more info.
+ *
+ * This interceptor also supports the following interfaces which can implemented by actions:
+ *
+ *
ValidationAware - implemented by ActionSupport class
+ *
ValidationWorkflowAware - allows changing result name programmatically
+ *
ValidationErrorAware - notifies action about errors and also allow change result name
+ *
+ *
+ * You can also use InputConfig annotation to change result name returned when validation errors occurred.
+ *
+ *
+ *
+ * Interceptor parameters:
+ *
+ *
+ *
+ *
inputResultName - Default to "input". Determine the result name to be returned when
+ * an action / field error is found.
+ *
+ *
+ *
+ * Extending the interceptor:
+ *
+ *
+ *
+ * There are no known extension points for this interceptor.
+ *
+ *
+ *
+ * Example code:
+ *
+ *
+ *
+ *
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="validation"/>
+ * <interceptor-ref name="workflow"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <-- In this case myMethod as well as mySecondMethod of the action class
+ * will not pass through the workflow process -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="validation"/>
+ * <interceptor-ref name="workflow">
+ * <param name="excludeMethods">myMethod,mySecondMethod</param>
+ * </interceptor-ref name="workflow">
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <-- In this case, the result named "error" will be used when
+ * an action / field error is found -->
+ * <-- The Interceptor will only be applied for myWorkflowMethod method of action
+ * classes, since this is the only included method while any others are excluded -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="validation"/>
+ * <interceptor-ref name="workflow">
+ * <param name="inputResultName">error</param>
+ * <param name="excludeMethods">*</param>
+ * <param name="includeMethods">myWorkflowMethod</param>
+ * </interceptor-ref>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ *
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * @author Alexandru Popescu
+ * @author Philip Luppens
+ * @author tm_jee
+ */
+public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
+
+ private static final long serialVersionUID = 7563014655616490865L;
+
+ private static final Logger LOG = LogManager.getLogger(DefaultWorkflowInterceptor.class);
+
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ private String inputResultName = Action.INPUT;
+
+ /**
+ * Set the inputResultName (result name to be returned when
+ * a action / field error is found registered). Default to {@link Action#INPUT}
+ *
+ * @param inputResultName what result name to use when there was validation error(s).
+ */
+ public void setInputResultName(String inputResultName) {
+ this.inputResultName = inputResultName;
+ }
+
+ /**
+ * Intercept {@link ActionInvocation} and returns a inputResultName
+ * when action / field errors is found registered.
+ *
+ * @return String result name
+ */
+ @Override
+ protected String doIntercept(ActionInvocation invocation) throws Exception {
+ Object action = invocation.getAction();
+
+ if (action instanceof ValidationAware) {
+ ValidationAware validationAwareAction = (ValidationAware) action;
+
+ if (validationAwareAction.hasErrors()) {
+ LOG.debug("Errors on action [{}], returning result name [{}]", validationAwareAction, inputResultName);
+
+ String resultName = inputResultName;
+ resultName = processValidationWorkflowAware(action, resultName);
+ resultName = processInputConfig(action, invocation.getProxy().getMethod(), resultName);
+ resultName = processValidationErrorAware(action, resultName);
+
+ return resultName;
+ }
+ }
+
+ return invocation.invoke();
+ }
+
+ /**
+ * Process {@link ValidationWorkflowAware} interface
+ */
+ private String processValidationWorkflowAware(final Object action, final String currentResultName) {
+ String resultName = currentResultName;
+ if (action instanceof ValidationWorkflowAware) {
+ resultName = ((ValidationWorkflowAware) action).getInputResultName();
+ LOG.debug("Changing result name from [{}] to [{}] because of processing [{}] interface applied to [{}]",
+ currentResultName, resultName, InputConfig.class.getSimpleName(), ValidationWorkflowAware.class.getSimpleName(), action);
+ }
+ return resultName;
+ }
+
+ /**
+ * Process {@link InputConfig} annotation applied to method
+ */
+ protected String processInputConfig(final Object action, final String method, final String currentResultName) throws Exception {
+ String resultName = currentResultName;
+ InputConfig annotation = action.getClass().getMethod(method, EMPTY_CLASS_ARRAY).getAnnotation(InputConfig.class);
+ if (annotation != null) {
+ if (StringUtils.isNotEmpty(annotation.methodName())) {
+ Method m = action.getClass().getMethod(annotation.methodName());
+ resultName = (String) m.invoke(action);
+ } else {
+ resultName = annotation.resultName();
+ }
+ LOG.debug("Changing result name from [{}] to [{}] because of processing annotation [{}] on action [{}]",
+ currentResultName, resultName, InputConfig.class.getSimpleName(), action);
+ }
+ return resultName;
+ }
+
+ /**
+ * Notify action if it implements {@see ValidationErrorAware} interface
+ */
+ protected String processValidationErrorAware(final Object action, final String currentResultName) {
+ String resultName = currentResultName;
+ if (action instanceof ValidationErrorAware) {
+ resultName = ((ValidationErrorAware) action).actionErrorOccurred(currentResultName);
+ LOG.debug("Changing result name from [{}] to [{}] because of processing interface [{}] on action [{}]",
+ currentResultName, resultName, ValidationErrorAware.class.getSimpleName(), action);
+ }
+ return resultName;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java
new file mode 100644
index 0000000..6959357
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionHolder.java
@@ -0,0 +1,77 @@
+/*
+ * 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.interceptor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Serializable;
+
+/**
+ *
+ *
+ * A simple wrapper around an exception, providing an easy way to print out the stack trace of the exception as well as
+ * a way to get a handle on the exception itself.
+ *
+ *
+ *
+ * @author Matthew E. Porter (matthew dot porter at metissian dot com)
+ */
+public class ExceptionHolder implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private Exception exception;
+
+ /**
+ * Holds the given exception
+ *
+ * @param exception the exception to hold.
+ */
+ public ExceptionHolder(Exception exception) {
+ this.exception = exception;
+ }
+
+ /**
+ * Gets the held exception
+ *
+ * @return the held exception
+ */
+ public Exception getException() {
+ return this.exception;
+ }
+
+ /**
+ * Gets the held exception stack trace using {@link Exception#printStackTrace()}.
+ *
+ * @return stack trace
+ */
+ public String getExceptionStack() {
+ String exceptionStack = null;
+
+ if (getException() != null) {
+ try (StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw)) {
+ getException().printStackTrace(pw);
+ exceptionStack = sw.toString();
+ } catch (IOException e) {
+ // Ignore exception generating stack trace.
+ }
+ }
+
+ return exceptionStack;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
new file mode 100644
index 0000000..d1a5319
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
@@ -0,0 +1,323 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ *
+ * This interceptor forms the core functionality of the exception handling feature. Exception handling allows you to map
+ * an exception to a result code, just as if the action returned a result code instead of throwing an unexpected
+ * exception. When an exception is encountered, it is wrapped with an {@link ExceptionHolder} and pushed on the stack,
+ * providing easy access to the exception from within your result.
+ *
+ * Note: While you can configure exception mapping in your configuration file at any point, the configuration
+ * will not have any effect if this interceptor is not in the interceptor stack for your actions. It is recommended that
+ * you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
+ * exception, even those caused by other interceptors.
+ *
+ *
+ *
+ * Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
logEnabled (optional) - Should exceptions also be logged? (boolean true|false)
+ *
+ *
logLevel (optional) - what log level should we use (trace, debug, info, warn, error, fatal)? - defaut is debug
+ *
+ *
logCategory (optional) - If provided we would use this category (eg. com.mycompany.app).
+ * Default is to use com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.
+ *
+ *
+ *
+ * The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
+ * and present a friendly webpage (with no stacktrace) to the end user.
+ *
+ *
+ *
+ * Extending the interceptor:
+ *
+ *
+ *
+ *
+ *
+ * If you want to add custom handling for publishing the Exception, you may override
+ * {@link #publishException(com.opensymphony.xwork2.ActionInvocation, ExceptionHolder)}. The default implementation
+ * pushes the given ExceptionHolder on value stack. A custom implementation could add additional logging etc.
+ *
+ *
+ *
+ * Example code:
+ *
+ *
+ *
+ * @author Matthew E. Porter (matthew dot porter at metissian dot com)
+ * @author Claus Ibsen
+ */
+public class ExceptionMappingInterceptor extends AbstractInterceptor {
+
+ protected static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
+
+ protected Logger categoryLogger;
+ protected boolean logEnabled = false;
+ protected String logCategory;
+ protected String logLevel;
+
+
+ public boolean isLogEnabled() {
+ return logEnabled;
+ }
+
+ public void setLogEnabled(boolean logEnabled) {
+ this.logEnabled = logEnabled;
+ }
+
+ public String getLogCategory() {
+ return logCategory;
+ }
+
+ public void setLogCategory(String logCatgory) {
+ this.logCategory = logCatgory;
+ }
+
+ public String getLogLevel() {
+ return logLevel;
+ }
+
+ public void setLogLevel(String logLevel) {
+ this.logLevel = logLevel;
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ String result;
+
+ try {
+ result = invocation.invoke();
+ } catch (Exception e) {
+ if (isLogEnabled()) {
+ handleLogging(e);
+ }
+ List exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
+ ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
+ if (mappingConfig != null && mappingConfig.getResult()!=null) {
+ Map parameterMap = mappingConfig.getParams();
+ // create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
+ invocation.getInvocationContext().setParameters(new HashMap(parameterMap));
+ result = mappingConfig.getResult();
+ publishException(invocation, new ExceptionHolder(e));
+ } else {
+ throw e;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Handles the logging of the exception.
+ *
+ * @param e the exception to log.
+ */
+ protected void handleLogging(Exception e) {
+ if (logCategory != null) {
+ if (categoryLogger == null) {
+ // init category logger
+ categoryLogger = LogManager.getLogger(logCategory);
+ }
+ doLog(categoryLogger, e);
+ } else {
+ doLog(LOG, e);
+ }
+ }
+
+ /**
+ * Performs the actual logging.
+ *
+ * @param logger the provided logger to use.
+ * @param e the exception to log.
+ */
+ protected void doLog(Logger logger, Exception e) {
+ if (logLevel == null) {
+ logger.debug(e.getMessage(), e);
+ return;
+ }
+
+ if ("trace".equalsIgnoreCase(logLevel)) {
+ logger.trace(e.getMessage(), e);
+ } else if ("debug".equalsIgnoreCase(logLevel)) {
+ logger.debug(e.getMessage(), e);
+ } else if ("info".equalsIgnoreCase(logLevel)) {
+ logger.info(e.getMessage(), e);
+ } else if ("warn".equalsIgnoreCase(logLevel)) {
+ logger.warn(e.getMessage(), e);
+ } else if ("error".equalsIgnoreCase(logLevel)) {
+ logger.error(e.getMessage(), e);
+ } else if ("fatal".equalsIgnoreCase(logLevel)) {
+ logger.fatal(e.getMessage(), e);
+ } else {
+ throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
+ }
+ }
+
+ /**
+ * @deprecated since 2.3.15 please use #findMappingFromExceptions directly instead
+ */
+ protected String findResultFromExceptions(List exceptionMappings, Throwable t) {
+ ExceptionMappingConfig result = findMappingFromExceptions(exceptionMappings, t);
+ return result==null?null:result.getResult();
+ }
+
+ /**
+ * Try to find appropriate {@link ExceptionMappingConfig} based on provided Throwable
+ *
+ * @param exceptionMappings list of defined exception mappings
+ * @param t caught exception
+ * @return appropriate mapping or null
+ */
+ protected ExceptionMappingConfig findMappingFromExceptions(List exceptionMappings, Throwable t) {
+ ExceptionMappingConfig config = null;
+ // Check for specific exception mappings.
+ if (exceptionMappings != null) {
+ int deepest = Integer.MAX_VALUE;
+ for (Object exceptionMapping : exceptionMappings) {
+ ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
+ int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
+ if (depth >= 0 && depth < deepest) {
+ deepest = depth;
+ config = exceptionMappingConfig;
+ }
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
+ * Otherwise, returns depth. Lowest depth wins.
+ *
+ * @param exceptionMapping the mapping classname
+ * @param t the cause
+ * @return the depth, if not found -1 is returned.
+ */
+ public int getDepth(String exceptionMapping, Throwable t) {
+ return getDepth(exceptionMapping, t.getClass(), 0);
+ }
+
+ private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
+ if (exceptionClass.getName().contains(exceptionMapping)) {
+ // Found it!
+ return depth;
+ }
+ // If we've gone as far as we can go and haven't found it...
+ if (exceptionClass.equals(Throwable.class)) {
+ return -1;
+ }
+ return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
+ }
+
+ /**
+ * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
+ * Subclasses may override this to customize publishing.
+ *
+ * @param invocation The invocation to publish Exception for.
+ * @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
+ */
+ protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
+ invocation.getStack().push(exceptionHolder);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java
new file mode 100644
index 0000000..afdd534
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java
@@ -0,0 +1,288 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.util.LocalizedTextUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ *
+ *
+ * An interceptor that handles setting the locale specified in a session as the locale for the current action request.
+ * In addition, this interceptor will look for a specific HTTP request parameter and set the locale to whatever value is
+ * provided. This means that this interceptor can be used to allow for your application to dynamically change the locale
+ * for the user's session or, alternatively, only for the current request (since XWork 2.1.3).
+ * This is very useful for applications that require multi-lingual support and want the user to
+ * be able to set his or her language preference at any point. The locale parameter is removed during the execution of
+ * this interceptor, ensuring that properties aren't set on an action (such as request_locale) that have no typical
+ * corresponding setter in your action.
+ *
+ * For example, using the default parameter name, a request to foo.action?request_locale=en_US, then the
+ * locale for US English is saved in the user's session and will be used for all future requests.
+ *
+ if there is no locale set (for example with the first visit), the interceptor uses the browser locale.
+ *
+ *
+ *
+ * Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
parameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to and save
+ * in the session. By default this is request_locale
+ *
+ *
requestOnlyParameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to
+ * for the current request only, without saving it in the session. By default this is request_only_locale
+ *
+ *
attributeName (optional) - the name of the session key to store the selected locale. By default this is
+ * WW_TRANS_I18N_LOCALE
+ *
+ *
+ *
+ *
+ *
+ * Extending the interceptor:
+ *
+ *
+ *
+ *
+ *
+ * There are no known extensions points for this interceptor.
+ *
+ *
+ *
+ * Example code:
+ *
+ *