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 delegate; + + final ReferenceType keyReferenceType; + final ReferenceType valueReferenceType; + + /** + * Concurrent hash map that wraps keys and/or values based on specified + * reference types. + * + * @param keyReferenceType key reference type + * @param valueReferenceType value reference type + */ + public ReferenceMap(ReferenceType keyReferenceType, + ReferenceType valueReferenceType) { + ensureNotNull(keyReferenceType, valueReferenceType); + + if (keyReferenceType == ReferenceType.PHANTOM || valueReferenceType == ReferenceType.PHANTOM) { + throw new IllegalArgumentException("Phantom references not supported."); + } + + this.delegate = new ConcurrentHashMap<>(); + this.keyReferenceType = keyReferenceType; + this.valueReferenceType = valueReferenceType; + } + + V internalGet(K key) { + Object valueReference = delegate.get(makeKeyReferenceAware(key)); + return valueReference == null ? null : (V) dereferenceValue(valueReference); + } + + public V get(final Object key) { + ensureNotNull(key); + return internalGet((K) key); + } + + V execute(Strategy strategy, K key, V value) { + ensureNotNull(key, value); + Object keyReference = referenceKey(key); + Object valueReference = strategy.execute(this, keyReference, referenceValue(keyReference, value)); + return valueReference == null ? null : (V) dereferenceValue(valueReference); + } + + public V put(K key, V value) { + return execute(putStrategy(), key, value); + } + + public V remove(Object key) { + ensureNotNull(key); + Object referenceAwareKey = makeKeyReferenceAware(key); + Object valueReference = delegate.remove(referenceAwareKey); + return valueReference == null ? null : (V) dereferenceValue(valueReference); + } + + public int size() { + return delegate.size(); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public boolean containsKey(Object key) { + ensureNotNull(key); + Object referenceAwareKey = makeKeyReferenceAware(key); + return delegate.containsKey(referenceAwareKey); + } + + public boolean containsValue(Object value) { + ensureNotNull(value); + for (Object valueReference : delegate.values()) { + if (value.equals(dereferenceValue(valueReference))) { + return true; + } + } + return false; + } + + public void putAll(Map t) { + for (Map.Entry entry : t.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + public void clear() { + delegate.clear(); + } + + /** + * Returns an unmodifiable set view of the keys in this map. As this method + * creates a defensive copy, the performance is O(n). + */ + public Set keySet() { + return Collections.unmodifiableSet(dereferenceKeySet(delegate.keySet())); + } + + /** + * Returns an unmodifiable set view of the values in this map. As this + * method creates a defensive copy, the performance is O(n). + */ + public Collection values() { + return Collections.unmodifiableCollection(dereferenceValues(delegate.values())); + } + + public V putIfAbsent(K key, V value) { + // TODO (crazybob) if the value has been gc'ed but the entry hasn't been + // cleaned up yet, this put will fail. + return execute(putIfAbsentStrategy(), key, value); + } + + public boolean remove(Object key, Object value) { + ensureNotNull(key, value); + Object referenceAwareKey = makeKeyReferenceAware(key); + Object referenceAwareValue = makeValueReferenceAware(value); + return delegate.remove(referenceAwareKey, referenceAwareValue); + } + + public boolean replace(K key, V oldValue, V newValue) { + ensureNotNull(key, oldValue, newValue); + Object keyReference = referenceKey(key); + + Object referenceAwareOldValue = makeValueReferenceAware(oldValue); + return delegate.replace(keyReference, referenceAwareOldValue, referenceValue(keyReference, newValue)); + } + + public V replace(K key, V value) { + // TODO (crazybob) if the value has been gc'ed but the entry hasn't been + // cleaned up yet, this will succeed when it probably shouldn't. + return execute(replaceStrategy(), key, value); + } + + /** + * Returns an unmodifiable set view of the entries in this map. As this + * method creates a defensive copy, the performance is O(n). + */ + public Set> entrySet() { + Set> entrySet = new HashSet<>(); + for (Map.Entry entry : delegate.entrySet()) { + Map.Entry dereferenced = dereferenceEntry(entry); + if (dereferenced != null) { + entrySet.add(dereferenced); + } + } + return Collections.unmodifiableSet(entrySet); + } + + /** + * Dereferences an entry. Returns null if the key or value has been gc'ed. + */ + Entry dereferenceEntry(Map.Entry entry) { + K key = dereferenceKey(entry.getKey()); + V value = dereferenceValue(entry.getValue()); + return (key == null || value == null) ? null : new Entry(key, value); + } + + /** + * Creates a reference for a key. + */ + Object referenceKey(K key) { + switch (keyReferenceType) { + case STRONG: + return key; + case SOFT: + return new SoftKeyReference(key); + case WEAK: + return new WeakKeyReference(key); + default: + throw new AssertionError(); + } + } + + /** + * Converts a reference to a key. + */ + K dereferenceKey(Object o) { + return (K) dereference(keyReferenceType, o); + } + + /** + * Converts a reference to a value. + */ + V dereferenceValue(Object o) { + return (V) dereference(valueReferenceType, o); + } + + /** + * Returns the refererent for reference given its reference type. + */ + Object dereference(ReferenceType referenceType, Object reference) { + return referenceType == STRONG ? reference : ((Reference) reference).get(); + } + + /** + * Creates a reference for a value. + */ + Object referenceValue(Object keyReference, Object value) { + switch (valueReferenceType) { + case STRONG: + return value; + case SOFT: + return new SoftValueReference(keyReference, value); + case WEAK: + return new WeakValueReference(keyReference, value); + default: + throw new AssertionError(); + } + } + + /** + * Dereferences a set of key references. + */ + Set dereferenceKeySet(Set keyReferences) { + return keyReferenceType == STRONG + ? keyReferences + : dereferenceCollection(keyReferenceType, keyReferences, new HashSet()); + } + + /** + * Dereferences a collection of value references. + */ + Collection dereferenceValues(Collection valueReferences) { + return valueReferenceType == STRONG + ? valueReferences + : dereferenceCollection(valueReferenceType, valueReferences, + new ArrayList(valueReferences.size())); + } + + /** + * Wraps key so it can be compared to a referenced key for equality. + */ + Object makeKeyReferenceAware(Object o) { + return keyReferenceType == STRONG ? o : new KeyReferenceAwareWrapper(o); + } + + /** + * Wraps value so it can be compared to a referenced value for equality. + */ + Object makeValueReferenceAware(Object o) { + return valueReferenceType == STRONG ? o : new ReferenceAwareWrapper(o); + } + + /** + * Dereferences elements in {@code in} using + * {@code referenceType} and puts them in {@code out}. Returns + * {@code out}. + */ + > T dereferenceCollection(ReferenceType referenceType, T in, T out) { + for (Object reference : in) { + out.add(dereference(referenceType, reference)); + } + return out; + } + + /** + * Marker interface to differentiate external and internal references. + */ + interface InternalReference { + } + + static int keyHashCode(Object key) { + return System.identityHashCode(key); + } + + /** + * Tests weak and soft references for identity equality. Compares references + * to other references and wrappers. If o is a reference, this returns true + * if r == o or if r and o reference the same non null object. If o is a + * wrapper, this returns true if r's referent is identical to the wrapped + * object. + */ + static boolean referenceEquals(Reference r, Object o) { + // compare reference to reference. + if (o instanceof InternalReference) { + // are they the same reference? used in cleanup. + if (o == r) { + return true; + } + + // do they reference identical values? used in conditional puts. + Object referent = ((Reference) o).get(); + return referent != null && referent == r.get(); + } + + // is the wrapped object identical to the referent? used in lookups. + return ((ReferenceAwareWrapper) o).unwrap() == r.get(); + } + + /** + * Big hack. Used to compare keys and values to referenced keys and values + * without creating more references. + */ + static class ReferenceAwareWrapper { + + Object wrapped; + + ReferenceAwareWrapper(Object wrapped) { + this.wrapped = wrapped; + } + + Object unwrap() { + return wrapped; + } + + @Override + public int hashCode() { + return wrapped.hashCode(); + } + + @Override + public boolean equals(Object obj) { + // defer to reference's equals() logic. + return obj.equals(this); + } + } + + /** + * Used for keys. Overrides hash code to use identity hash code. + */ + static class KeyReferenceAwareWrapper extends ReferenceAwareWrapper { + + public KeyReferenceAwareWrapper(Object wrapped) { + super(wrapped); + } + + @Override + public int hashCode() { + return System.identityHashCode(wrapped); + } + } + + class SoftKeyReference extends FinalizableSoftReference implements InternalReference { + + int hashCode; + + public SoftKeyReference(Object key) { + super(key); + this.hashCode = keyHashCode(key); + } + + public void finalizeReferent() { + delegate.remove(this); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public boolean equals(Object o) { + return referenceEquals(this, o); + } + } + + class WeakKeyReference extends FinalizableWeakReference implements InternalReference { + + int hashCode; + + public WeakKeyReference(Object key) { + super(key); + this.hashCode = keyHashCode(key); + } + + public void finalizeReferent() { + delegate.remove(this); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public boolean equals(Object o) { + return referenceEquals(this, o); + } + } + + class SoftValueReference extends FinalizableSoftReference implements InternalReference { + + Object keyReference; + + public SoftValueReference(Object keyReference, Object value) { + super(value); + this.keyReference = keyReference; + } + + public void finalizeReferent() { + delegate.remove(keyReference, this); + } + + @Override + public boolean equals(Object obj) { + return referenceEquals(this, obj); + } + } + + class WeakValueReference extends FinalizableWeakReference implements InternalReference { + + Object keyReference; + + public WeakValueReference(Object keyReference, Object value) { + super(value); + this.keyReference = keyReference; + } + + public void finalizeReferent() { + delegate.remove(keyReference, this); + } + + @Override + public boolean equals(Object obj) { + return referenceEquals(this, obj); + } + } + + protected interface Strategy { + public Object execute(ReferenceMap map, Object keyReference, Object valueReference); + } + + protected Strategy putStrategy() { + return PutStrategy.PUT; + } + + protected Strategy putIfAbsentStrategy() { + return PutStrategy.PUT_IF_ABSENT; + } + + protected Strategy replaceStrategy() { + return PutStrategy.REPLACE; + } + + private enum PutStrategy implements Strategy { + PUT { + public Object execute(ReferenceMap map, Object keyReference, Object valueReference) { + return map.delegate.put(keyReference, valueReference); + } + }, + + REPLACE { + public Object execute(ReferenceMap map, Object keyReference, Object valueReference) { + return map.delegate.replace(keyReference, valueReference); + } + }, + + PUT_IF_ABSENT { + public Object execute(ReferenceMap map, Object keyReference, Object valueReference) { + return map.delegate.putIfAbsent(keyReference, valueReference); + } + }; + } + + private static PutStrategy defaultPutStrategy; + + protected PutStrategy getPutStrategy() { + return defaultPutStrategy; + } + + + class Entry implements Map.Entry { + + K key; + V value; + + public Entry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return this.key; + } + + public V getValue() { + return this.value; + } + + public V setValue(V value) { + return put(key, value); + } + + @Override + public int hashCode() { + return key.hashCode() * 31 + value.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ReferenceMap.Entry)) { + return false; + } + + Entry entry = (Entry) o; + return key.equals(entry.key) && value.equals(entry.value); + } + + @Override + public String toString() { + return key + "=" + value; + } + } + + static void ensureNotNull(Object o) { + if (o == null) { + throw new NullPointerException(); + } + } + + static void ensureNotNull(Object... array) { + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + throw new NullPointerException("Argument #" + i + " is null."); + } + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(size()); + for (Map.Entry entry : delegate.entrySet()) { + Object key = dereferenceKey(entry.getKey()); + Object value = dereferenceValue(entry.getValue()); + + // don't persist gc'ed entries. + if (key != null && value != null) { + out.writeObject(key); + out.writeObject(value); + } + } + out.writeObject(null); + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + int size = in.readInt(); + this.delegate = new ConcurrentHashMap(size); + while (true) { + K key = (K) in.readObject(); + if (key == null) { + break; + } + V value = (V) in.readObject(); + put(key, value); + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java new file mode 100644 index 0000000..a223a00 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceType.java @@ -0,0 +1,55 @@ +/** + * 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; + +/** + * Reference type. Used to specify what type of reference to keep to a + * referent. + * + * @see java.lang.ref.Reference + * @author crazybob@google.com (Bob Lee) + */ +public enum ReferenceType { + + /** + * Prevents referent from being reclaimed by the garbage collector. + */ + STRONG, + + /** + * Referent reclaimed in an LRU fashion when the VM runs low on memory and + * no strong references exist. + * + * @see java.lang.ref.SoftReference + */ + SOFT, + + /** + * Referent reclaimed when no strong or soft references exist. + * + * @see java.lang.ref.WeakReference + */ + WEAK, + + /** + * Similar to weak references except the garbage collector doesn't actually + * reclaim the referent. More flexible alternative to finalization. + * + * @see java.lang.ref.PhantomReference + */ + PHANTOM; +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java new file mode 100644 index 0000000..a15291a --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/Strings.java @@ -0,0 +1,55 @@ +/** + * 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; + +/** + * String utilities. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Strings { + + /** + * Returns a string that is equivalent to the specified string with its + * first character converted to uppercase as by {@link String#toUpperCase}. + * The returned string will have the same value as the specified string if + * its first character is non-alphabetic, if its first character is already + * uppercase, or if the specified string is of length 0. + * + *

For example: + *

+   *    capitalize("foo bar").equals("Foo bar");
+   *    capitalize("2b or not 2b").equals("2b or not 2b")
+   *    capitalize("Foo bar").equals("Foo bar");
+   *    capitalize("").equals("");
+   * 
+ * + * @param s the string whose first character is to be uppercased + * @return a string equivalent to s with its first character + * converted to uppercase + * @throws NullPointerException if s is null + */ + public static String capitalize(String s) { + if (s.length() == 0) + return s; + char first = s.charAt(0); + char capitalized = Character.toUpperCase(first); + return (first == capitalized) + ? s + : capitalized + s.substring(1); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/package.html ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/package.html b/core/src/main/java/com/opensymphony/xwork2/inject/util/package.html new file mode 100644 index 0000000..e326461 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/package.html @@ -0,0 +1 @@ +Guice util classes. http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java new file mode 100644 index 0000000..470d349 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * Provides default implementations of optional lifecycle methods + */ +public abstract class AbstractInterceptor implements Interceptor { + + /** + * Does nothing + */ + public void init() { + } + + /** + * Does nothing + */ + public void destroy() { + } + + + /** + * Override to handle interception + */ + public abstract String intercept(ActionInvocation invocation) throws Exception; +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java new file mode 100644 index 0000000..7bd9499 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.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.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.LocalizedTextUtil; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; + + +/** + * + * + * The aim of this Interceptor is to alias a named parameter to a different named parameter. By acting as the glue + * between actions sharing similiar parameters (but with different names), it can help greatly with action chaining. + * + *

Action's alias expressions should be in the form of #{ "name1" : "alias1", "name2" : "alias2" }. + * This means that assuming an action (or something else in the stack) has a value for the expression named name1 and the + * action this interceptor is applied to has a setter named alias1, alias1 will be set with the value from + * name1. + * + * + * + *

Interceptor parameters: + * + * + * + *

    + * + *
  • aliasesKey (optional) - the name of the action parameter to look for the alias map (by default this is + * aliases).
  • + * + *
+ * + * + * + *

Extending the interceptor: + * + *

+ * + * + * + * This interceptor does not have any known extension points. + * + * + * + *

Example code: + * + *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <!-- The value for the foo parameter will be applied as if it were named bar -->
+ *     <param name="aliases">#{ 'foo' : 'bar' }</param>
+ *
+ *     <interceptor-ref name="alias"/>
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Matthew Payne + */ +public class AliasInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LogManager.getLogger(AliasInterceptor.class); + + private static final String DEFAULT_ALIAS_KEY = "aliases"; + protected String aliasesKey = DEFAULT_ALIAS_KEY; + + protected ValueStackFactory valueStackFactory; + static boolean devMode = false; + + @Inject(XWorkConstants.DEV_MODE) + public static void setDevMode(String mode) { + devMode = "true".equals(mode); + } + + @Inject + public void setValueStackFactory(ValueStackFactory valueStackFactory) { + this.valueStackFactory = valueStackFactory; + } + + /** + * Sets the name of the action parameter to look for the alias map. + *

+ * Default is aliases. + * + * @param aliasesKey the name of the action parameter + */ + public void setAliasesKey(String aliasesKey) { + this.aliasesKey = aliasesKey; + } + + @Override public String intercept(ActionInvocation invocation) throws Exception { + + ActionConfig config = invocation.getProxy().getConfig(); + ActionContext ac = invocation.getInvocationContext(); + Object action = invocation.getAction(); + + // get the action's parameters + final Map parameters = config.getParams(); + + if (parameters.containsKey(aliasesKey)) { + + String aliasExpression = parameters.get(aliasesKey); + ValueStack stack = ac.getValueStack(); + Object obj = stack.findValue(aliasExpression); + + if (obj != null && obj instanceof Map) { + //get secure stack + ValueStack newStack = valueStackFactory.createValueStack(stack); + boolean clearableStack = newStack instanceof ClearableValueStack; + if (clearableStack) { + //if the stack's context can be cleared, do that to prevent OGNL + //from having access to objects in the stack, see XW-641 + ((ClearableValueStack)newStack).clearContextValues(); + Map context = newStack.getContext(); + ReflectionContextState.setCreatingNullObjects(context, true); + ReflectionContextState.setDenyMethodExecution(context, true); + ReflectionContextState.setReportingConversionErrors(context, true); + + //keep locale from original context + context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE)); + } + + // override + Map aliases = (Map) obj; + for (Object o : aliases.entrySet()) { + Map.Entry entry = (Map.Entry) o; + String name = entry.getKey().toString(); + String alias = (String) entry.getValue(); + Object value = stack.findValue(name); + if (null == value) { + // workaround + Map contextParameters = ActionContext.getContext().getParameters(); + + if (null != contextParameters) { + value = contextParameters.get(name); + } + } + if (null != value) { + try { + newStack.setValue(alias, value); + } catch (RuntimeException e) { + if (devMode) { + String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{ + "Unexpected Exception caught setting '" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage() + }); + LOG.error(developerNotification); + if (action instanceof ValidationAware) { + ((ValidationAware) action).addActionMessage(developerNotification); + } + } + } + } + } + + if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null)) + stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS)); + } else { + LOG.debug("invalid alias expression: {}", aliasesKey); + } + } + + return invocation.invoke(); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java new file mode 100644 index 0000000..8218e1b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java @@ -0,0 +1,230 @@ +/* + * 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.ActionChainResult; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.Unchainable; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.CompoundRoot; +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + +import java.util.*; + + +/** + * + *

+ * An interceptor that copies all the properties of every object in the value stack to the currently executing object, + * except for any object that implements {@link Unchainable}. A collection of optional includes and + * excludes may be provided to control how and which parameters are copied. Only includes or excludes may be + * specified. Specifying both results in undefined behavior. See the javadocs for {@link ReflectionProvider#copy(Object, Object, + * java.util.Map, java.util.Collection, java.util.Collection)} for more information. + *

+ *

+ * Note: It is important to remember that this interceptor does nothing if there are no objects already on the stack. + *
This means two things: + *
One, you can safely apply it to all your actions without any worry of adverse affects. + *
Two, it is up to you to ensure an object exists in the stack prior to invoking this action. The most typical way this is done + * is through the use of the chain result type, which combines with this interceptor to make up the action + * chaining feature. + *

+ * Note: By default Errors, Field errors and Message aren't copied during chaining, to change the behaviour you can specify + * the below three constants in struts.properties or struts.xml: + *

    + *
  • struts.xwork.chaining.copyErrors - set to true to copy Action Errors
  • + *
  • struts.xwork.chaining.copyFieldErrors - set to true to copy Field Errors
  • + *
  • struts.xwork.chaining.copyMessages - set to true to copy Action Messages
  • + *
+ *

+ *

+ * Example: + *

+ * <constant name="struts.xwork.chaining.copyErrors" value="true"/>
+ * 
+ *

+ * 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. + *

+ * + * Example code: + *
+ * 
+ * 

+ * <action name="someAction" class="com.examples.SomeAction"> + * <interceptor-ref name="basicStack"/> + * <result name="success" type="chain">otherAction</result> + * </action> + *

+ * <action name="otherAction" class="com.examples.OtherAction"> + * <interceptor-ref name="chain"/> + * <interceptor-ref name="basicStack"/> + * <result name="success">good_result.ftl</result> + * </action> + *

+ * + *

+ * + * @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: + * + *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="params"/>
+ *     <interceptor-ref name="conversionError"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @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: + * + *

+ * 
+ * <xwork>
+ *     <package name="default" extends="xwork-default">
+ *         <global-results>
+ *             <result name="error" type="freemarker">error.ftl</result>
+ *         </global-results>
+ *
+ *         <global-exception-mappings>
+ *             <exception-mapping exception="java.lang.Exception" result="error"/>
+ *         </global-exception-mappings>
+ *
+ *         <action name="test">
+ *             <interceptor-ref name="exception"/>
+ *             <interceptor-ref name="basicStack"/>
+ *             <exception-mapping exception="com.acme.CustomException" result="custom_error"/>
+ *             <result name="custom_error">custom_error.ftl</result>
+ *             <result name="success" type="freemarker">test.ftl</result>
+ *         </action>
+ *     </package>
+ * </xwork>
+ * 
+ * 
+ * + *

+ * This second example will also log the exceptions using our own category + * com.mycompany.app.unhandled at WARN level. + * + *

+ * 
+ * <xwork>
+ *   <package name="something" extends="xwork-default">
+ *      <interceptors>
+ *          <interceptor-stack name="exceptionmappingStack">
+ *              <interceptor-ref name="exception">
+ *                  <param name="logEnabled">true</param>
+ *                  <param name="logCategory">com.mycompany.app.unhandled</param>
+ *                  <param name="logLevel">WARN</param>	        		
+ *              </interceptor-ref>	
+ *              <interceptor-ref name="i18n"/>
+ *              <interceptor-ref name="staticParams"/>
+ *              <interceptor-ref name="params"/>
+ *              <interceptor-ref name="validation">
+ *                  <param name="excludeMethods">input,back,cancel,browse</param>
+ *              </interceptor-ref>
+ *          </interceptor-stack>
+ *      </interceptors>
+ *
+ *      <default-interceptor-ref name="exceptionmappingStack"/>
+ *    
+ *      <global-results>
+ *           <result name="unhandledException">/unhandled-exception.jsp</result>
+ *      </global-results>
+ *
+ *      <global-exception-mappings>
+ *           <exception-mapping exception="java.lang.Exception" result="unhandledException"/>
+ *      </global-exception-mappings>
+ *        
+ *      <action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction">
+ *          <exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
+ *                             result="damm"/>
+ *          <result name="input">index.jsp</result>
+ *          <result name="success">success.jsp</result>            
+ *          <result name="damm">damm.jsp</result>
+ *      </action>
+ *
+ *   </package>
+ * </xwork>
+ * 
+ * 
+ * + * @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: + *

+ *

+ * 
+ * <action name="someAction" class="com.examples.SomeAction">
+ *     <interceptor-ref name="i18n"/>
+ *     <interceptor-ref name="basicStack"/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * @author Aleksei Gopachenko + */ +public class I18nInterceptor extends AbstractInterceptor { + private static final long serialVersionUID = 2496830135246700300L; + + protected static final Logger LOG = LogManager.getLogger(I18nInterceptor.class); + + public static final String DEFAULT_SESSION_ATTRIBUTE = "WW_TRANS_I18N_LOCALE"; + public static final String DEFAULT_PARAMETER = "request_locale"; + public static final String DEFAULT_REQUESTONLY_PARAMETER = "request_only_locale"; + + protected String parameterName = DEFAULT_PARAMETER; + protected String requestOnlyParameterName = DEFAULT_REQUESTONLY_PARAMETER; + protected String attributeName = DEFAULT_SESSION_ATTRIBUTE; + + // Request-Only = None + protected enum Storage { SESSION, NONE } + + public I18nInterceptor() { + LOG.debug("new I18nInterceptor()"); + } + + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } + + public void setRequestOnlyParameterName(String requestOnlyParameterName) { + this.requestOnlyParameterName = requestOnlyParameterName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + if (LOG.isDebugEnabled()) { + LOG.debug("Intercept '{}/{}' {", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName()); + } + + LocaleFinder localeFinder = new LocaleFinder(invocation); + Locale locale = getLocaleFromParam(localeFinder.getRequestedLocale()); + locale = storeLocale(invocation, locale, localeFinder.getStorage()); + saveLocale(invocation, locale); + + if (LOG.isDebugEnabled()) { + LOG.debug("before Locale: {}", invocation.getStack().findValue("locale")); + } + + final String result = invocation.invoke(); + + if (LOG.isDebugEnabled()) { + LOG.debug("after Locale {}", invocation.getStack().findValue("locale")); + LOG.debug("intercept } "); + } + + return result; + } + + /** + * Store the locale to the chosen storage, like f. e. the session + * + * @param invocation the action invocation + * @param locale the locale to store + * @param storage the place to store this locale (like Storage.SESSSION.toString()) + */ + protected Locale storeLocale(ActionInvocation invocation, Locale locale, String storage) { + //save it in session + Map session = invocation.getInvocationContext().getSession(); + + if (session != null) { + synchronized (session) { + if (locale == null) { + storage = Storage.NONE.toString(); + locale = readStoredLocale(invocation, session); + } + + if (Storage.SESSION.toString().equals(storage)) { + session.put(attributeName, locale); + } + } + } + return locale; + } + + protected class LocaleFinder { + protected String storage = Storage.SESSION.toString(); + protected Object requestedLocale = null; + + protected ActionInvocation actionInvocation = null; + + protected LocaleFinder(ActionInvocation invocation) { + actionInvocation = invocation; + find(); + } + + protected void find() { + //get requested locale + Map params = actionInvocation.getInvocationContext().getParameters(); + + storage = Storage.SESSION.toString(); + + requestedLocale = findLocaleParameter(params, parameterName); + if (requestedLocale != null) { + return; + } + + requestedLocale = findLocaleParameter(params, requestOnlyParameterName); + if (requestedLocale != null) { + storage = Storage.NONE.toString(); + } + } + + public String getStorage() { + return storage; + } + + public Object getRequestedLocale() { + return requestedLocale; + } + } + + /** + * Creates a Locale object from the request param, which might + * be already a Local or a String + * + * @param requestedLocale the parameter from the request + * @return the Locale + */ + protected Locale getLocaleFromParam(Object requestedLocale) { + Locale locale = null; + if (requestedLocale != null) { + locale = (requestedLocale instanceof Locale) ? + (Locale) requestedLocale : + LocalizedTextUtil.localeFromString(requestedLocale.toString(), null); + if (locale != null) { + LOG.debug("Applied request locale: {}", locale); + } + } + return locale; + } + + /** + * Reads the locale from the session, and if not found from the + * current invocation (=browser) + * + * @param invocation the current invocation + * @param session the current session + * @return the read locale + */ + protected Locale readStoredLocale(ActionInvocation invocation, Map session) { + Locale locale = this.readStoredLocalFromSession(invocation, session); + + if (locale != null) { + return locale; + } + + return this.readStoredLocalFromCurrentInvocation(invocation); + } + + protected Locale readStoredLocalFromSession(ActionInvocation invocation, Map session) { + // check session for saved locale + Object sessionLocale = session.get(attributeName); + if (sessionLocale != null && sessionLocale instanceof Locale) { + Locale locale = (Locale) sessionLocale; + LOG.debug("Applied session locale: {}", locale); + return locale; + } + return null; + } + + protected Locale readStoredLocalFromCurrentInvocation(ActionInvocation invocation) { + // no overriding locale definition found, stay with current invocation (=browser) locale + Locale locale = invocation.getInvocationContext().getLocale(); + if (locale != null) { + LOG.debug("Applied invocation context locale: {}", locale); + } + return locale; + } + + protected Object findLocaleParameter(Map params, String parameterName) { + Object requestedLocale = params.remove(parameterName); + if (requestedLocale != null && requestedLocale.getClass().isArray() + && ((Object[]) requestedLocale).length > 0) { + requestedLocale = ((Object[]) requestedLocale)[0]; + + LOG.debug("Requested locale: {}", requestedLocale); + } + return requestedLocale; + } + + /** + * Save the given locale to the ActionInvocation. + * + * @param invocation The ActionInvocation. + * @param locale The locale to save. + */ + protected void saveLocale(ActionInvocation invocation, Locale locale) { + invocation.getInvocationContext().setLocale(locale); + } + +}