You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2015/06/17 23:09:32 UTC

[32/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/util/location/LocationAttributes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/LocationAttributes.java b/core/src/main/java/com/opensymphony/xwork2/util/location/LocationAttributes.java
new file mode 100644
index 0000000..5ca0934
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/location/LocationAttributes.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.location;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * A class to handle location information stored in attributes.
+ * These attributes are typically setup using {@link com.opensymphony.xwork2.util.location.LocationAttributes.Pipe}
+ * which augments the SAX stream with additional attributes, e.g.:
+ * <pre>
+ * &lt;root xmlns:loc="http://struts.apache.org/xwork/location"
+ *       loc:src="file://path/to/file.xml"
+ *       loc:line="1" loc:column="1"&gt;
+ *   &lt;foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/&gt;
+ * &lt;/root&gt;
+ * </pre>
+ * 
+ * @see com.opensymphony.xwork2.util.location.LocationAttributes.Pipe
+ * @since 2.1.8
+ * @version $Id$
+ */
+public class LocationAttributes {
+    /** Prefix for the location namespace */
+    public static final String PREFIX = "loc";
+    /** Namespace URI for location attributes */
+    public static final String URI = "http://struts.apache.org/xwork/location";
+
+    /** Attribute name for the location URI */
+    public static final String SRC_ATTR  = "src";
+    /** Attribute name for the line number */
+    public static final String LINE_ATTR = "line";
+    /** Attribute name for the column number */
+    public static final String COL_ATTR  = "column";
+
+    /** Attribute qualified name for the location URI */
+    public static final String Q_SRC_ATTR  = "loc:src";
+    /** Attribute qualified name for the line number */
+    public static final String Q_LINE_ATTR = "loc:line";
+    /** Attribute qualified name for the column number */
+    public static final String Q_COL_ATTR  = "loc:column";
+    
+    // Private constructor, we only have static methods
+    private LocationAttributes() {
+        // Nothing
+    }
+    
+    /**
+     * Add location attributes to a set of SAX attributes.
+     * 
+     * @param locator the <code>Locator</code> (can be null)
+     * @param attrs the <code>Attributes</code> where locator information should be added
+     * @return Location enabled Attributes.
+     */
+    public static Attributes addLocationAttributes(Locator locator, Attributes attrs) {
+        if (locator == null || attrs.getIndex(URI, SRC_ATTR) != -1) {
+            // No location information known, or already has it
+            return attrs;
+        }
+        
+        // Get an AttributeImpl so that we can add new attributes.
+        AttributesImpl newAttrs = attrs instanceof AttributesImpl ?
+            (AttributesImpl)attrs : new AttributesImpl(attrs);
+
+        newAttrs.addAttribute(URI, SRC_ATTR, Q_SRC_ATTR, "CDATA", locator.getSystemId());
+        newAttrs.addAttribute(URI, LINE_ATTR, Q_LINE_ATTR, "CDATA", Integer.toString(locator.getLineNumber()));
+        newAttrs.addAttribute(URI, COL_ATTR, Q_COL_ATTR, "CDATA", Integer.toString(locator.getColumnNumber()));
+        
+        return newAttrs;
+    }
+    
+    /**
+     * Returns the {@link Location} of an element (SAX flavor).
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @param description a description for the location (can be null)
+     * @return a {@link Location} object
+     */
+    public static Location getLocation(Attributes attrs, String description) {
+        String src = attrs.getValue(URI, SRC_ATTR);
+        if (src == null) {
+            return Location.UNKNOWN;
+        }
+        
+        return new LocationImpl(description, src, getLine(attrs), getColumn(attrs));
+    }
+
+    /**
+     * Returns the location of an element (SAX flavor). If the location is to be kept
+     * into an object built from this element, consider using {@link #getLocation(Attributes, String)}
+     * and the {@link Locatable} interface.
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return a location string as defined by {@link Location}.
+     */
+    public static String getLocationString(Attributes attrs) {
+        String src = attrs.getValue(URI, SRC_ATTR);
+        if (src == null) {
+            return LocationUtils.UNKNOWN_STRING;
+        }
+        
+        return src + ":" + attrs.getValue(URI, LINE_ATTR) + ":" + attrs.getValue(URI, COL_ATTR);
+    }
+    
+    /**
+     * Returns the URI of an element (SAX flavor)
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return the element's URI or "<code>[unknown location]</code>" if <code>attrs</code>
+     *         has no location information.
+     */
+    public static String getURI(Attributes attrs) {
+        String src = attrs.getValue(URI, SRC_ATTR);
+        return src != null ? src : LocationUtils.UNKNOWN_STRING;
+    }
+    
+    /**
+     * Returns the line number of an element (SAX flavor)
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return the element's line number or <code>-1</code> if <code>attrs</code>
+     *         has no location information.
+     */
+    public static int getLine(Attributes attrs) {
+        String line = attrs.getValue(URI, LINE_ATTR);
+        return line != null ? Integer.parseInt(line) : -1;
+    }
+    
+    /**
+     * Returns the column number of an element (SAX flavor)
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return the element's column number or <code>-1</code> if <code>attrs</code>
+     *         has no location information.
+     */
+    public static int getColumn(Attributes attrs) {
+        String col = attrs.getValue(URI, COL_ATTR);
+        return col != null ? Integer.parseInt(col) : -1;
+    }
+    
+    /**
+     * Returns the {@link Location} of an element (DOM flavor).
+     * 
+     * @param elem the element that holds the location information
+     * @param description a description for the location (if <code>null</code>, the element's name is used)
+     * @return a {@link Location} object
+     */
+    public static Location getLocation(Element elem, String description) {
+        Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
+        if (srcAttr == null) {
+            return Location.UNKNOWN;
+        }
+
+        return new LocationImpl(description == null ? elem.getNodeName() : description,
+                srcAttr.getValue(), getLine(elem), getColumn(elem));
+    }
+    
+    /**
+     * Same as <code>getLocation(elem, null)</code>.
+     */
+    public static Location getLocation(Element elem) {
+        return getLocation(elem, null);
+    }
+   
+
+    /**
+     * Returns the location of an element that has been processed by this pipe (DOM flavor).
+     * If the location is to be kept into an object built from this element, consider using
+     * {@link #getLocation(Element)} and the {@link Locatable} interface.
+     * 
+     * @param elem the element that holds the location information
+     * @return a location string as defined by {@link Location}.
+     */
+    public static String getLocationString(Element elem) {
+        Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
+        if (srcAttr == null) {
+            return LocationUtils.UNKNOWN_STRING;
+        }
+        
+        return srcAttr.getValue() + ":" + elem.getAttributeNS(URI, LINE_ATTR) + ":" + elem.getAttributeNS(URI, COL_ATTR);
+    }
+    
+    /**
+     * Returns the URI of an element (DOM flavor)
+     * 
+     * @param elem the element that holds the location information
+     * @return the element's URI or "<code>[unknown location]</code>" if <code>elem</code>
+     *         has no location information.
+     */
+    public static String getURI(Element elem) {
+        Attr attr = elem.getAttributeNodeNS(URI, SRC_ATTR);
+        return attr != null ? attr.getValue() : LocationUtils.UNKNOWN_STRING;
+    }
+
+    /**
+     * Returns the line number of an element (DOM flavor)
+     * 
+     * @param elem the element that holds the location information
+     * @return the element's line number or <code>-1</code> if <code>elem</code>
+     *         has no location information.
+     */
+    public static int getLine(Element elem) {
+        Attr attr = elem.getAttributeNodeNS(URI, LINE_ATTR);
+        return attr != null ? Integer.parseInt(attr.getValue()) : -1;
+    }
+
+    /**
+     * Returns the column number of an element (DOM flavor)
+     * 
+     * @param elem the element that holds the location information
+     * @return the element's column number or <code>-1</code> if <code>elem</code>
+     *         has no location information.
+     */
+    public static int getColumn(Element elem) {
+        Attr attr = elem.getAttributeNodeNS(URI, COL_ATTR);
+        return attr != null ? Integer.parseInt(attr.getValue()) : -1;
+    }
+    
+    /**
+     * Remove the location attributes from a DOM element.
+     * 
+     * @param elem the element to remove the location attributes from.
+     * @param recurse if <code>true</code>, also remove location attributes on descendant elements.
+     */
+    public static void remove(Element elem, boolean recurse) {
+        elem.removeAttributeNS(URI, SRC_ATTR);
+        elem.removeAttributeNS(URI, LINE_ATTR);
+        elem.removeAttributeNS(URI, COL_ATTR);
+        if (recurse) {
+            NodeList children = elem.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    remove((Element)child, recurse);
+                }
+            }
+        }
+    }
+
+    /**
+     * A SAX filter that adds the information available from the <code>Locator</code> as attributes.
+     * The purpose of having location as attributes is to allow this information to survive transformations
+     * of the document (an XSL could copy these attributes over) or conversion of SAX events to a DOM.
+     * <p>
+     * The location is added as 3 attributes in a specific namespace to each element.
+     * <pre>
+     * &lt;root xmlns:loc="http://opensymphony.com/xwork/location"
+     *       loc:src="file://path/to/file.xml"
+     *       loc:line="1" loc:column="1"&gt;
+     *   &lt;foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/&gt;
+     * &lt;/root&gt;
+     * </pre>
+     * <strong>Note:</strong> Although this adds a lot of information to the serialized form of the document,
+     * the overhead in SAX events is not that big, as attribute names are interned, and all <code>src</code>
+     * attributes point to the same string.
+     * 
+     * @see com.opensymphony.xwork2.util.location.LocationAttributes
+     */
+    public static class Pipe implements ContentHandler {
+        
+        private Locator locator;
+        
+        private ContentHandler nextHandler;
+        
+        /**
+         * Create a filter. It has to be chained to another handler to be really useful.
+         */
+        public Pipe() {
+        }
+
+        /**
+         * Create a filter that is chained to another handler.
+         * @param next the next handler in the chain.
+         */
+        public Pipe(ContentHandler next) {
+            nextHandler = next;
+        }
+
+        public void setDocumentLocator(Locator locator) {
+            this.locator = locator;
+            nextHandler.setDocumentLocator(locator);
+        }
+        
+        public void startDocument() throws SAXException {
+            nextHandler.startDocument();
+            nextHandler.startPrefixMapping(LocationAttributes.PREFIX, LocationAttributes.URI);
+        }
+        
+        public void endDocument() throws SAXException {
+            endPrefixMapping(LocationAttributes.PREFIX);
+            nextHandler.endDocument();
+        }
+
+        public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException {
+            // Add location attributes to the element
+            nextHandler.startElement(uri, loc, raw, LocationAttributes.addLocationAttributes(locator, attrs));
+        }
+
+        public void endElement(String arg0, String arg1, String arg2) throws SAXException {
+            nextHandler.endElement(arg0, arg1, arg2);
+        }
+
+        public void startPrefixMapping(String arg0, String arg1) throws SAXException {
+            nextHandler.startPrefixMapping(arg0, arg1);
+        }
+
+        public void endPrefixMapping(String arg0) throws SAXException {
+            nextHandler.endPrefixMapping(arg0);
+        }
+
+        public void characters(char[] arg0, int arg1, int arg2) throws SAXException {
+            nextHandler.characters(arg0, arg1, arg2);
+        }
+
+        public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
+            nextHandler.ignorableWhitespace(arg0, arg1, arg2);
+        }
+
+        public void processingInstruction(String arg0, String arg1) throws SAXException {
+            nextHandler.processingInstruction(arg0, arg1);
+        }
+
+        public void skippedEntity(String arg0) throws SAXException {
+            nextHandler.skippedEntity(arg0);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java b/core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java
new file mode 100644
index 0000000..ca101ca
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/location/LocationImpl.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.location;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A simple immutable and serializable implementation of {@link Location}.
+ */
+public class LocationImpl implements Location, Serializable {
+
+    private final String uri;
+    private final int line;
+    private final int column;
+    private final String description;
+    
+    // Package private: outside this package, use Location.UNKNOWN.
+    static final LocationImpl UNKNOWN = new LocationImpl(null, null, -1, -1);
+
+    /**
+     * Build a location for a given URI, with unknown line and column numbers.
+     * 
+     * @param uri the resource URI
+     */
+    public LocationImpl(String description, String uri) {
+        this(description, uri, -1, -1);
+    }
+
+    /**
+     * Build a location for a given URI and line and column numbers.
+     * 
+     * @param uri the resource URI
+     * @param line the line number (starts at 1)
+     * @param column the column number (starts at 1)
+     */
+    public LocationImpl(String description, String uri, int line, int column) {
+        if (StringUtils.isEmpty(uri)) {
+            this.uri = null;
+            this.line = -1;
+            this.column = -1;
+        } else {
+            this.uri = uri;
+            this.line = line;
+            this.column = column;
+        }
+        this.description = StringUtils.trimToNull(description);
+    }
+    
+    /**
+     * Copy constructor.
+     * 
+     * @param location the location to be copied
+     */
+    public LocationImpl(Location location) {
+        this(location.getDescription(), location.getURI(), location.getLineNumber(), location.getColumnNumber());
+    }
+    
+    /**
+     * Create a location from an existing one, but with a different description
+     */
+    public LocationImpl(String description, Location location) {
+        this(description, location.getURI(), location.getLineNumber(), location.getColumnNumber());
+    }
+    
+    /**
+     * Obtain a <code>LocationImpl</code> from a {@link Location}. If <code>location</code> is
+     * already a <code>LocationImpl</code>, it is returned, otherwise it is copied.
+     * <p>
+     * This method is useful when an immutable and serializable location is needed, such as in locatable
+     * exceptions.
+     * 
+     * @param location the location
+     * @return an immutable and serializable version of <code>location</code>
+     */
+    public static LocationImpl get(Location location) {
+        if (location instanceof LocationImpl) {
+            return (LocationImpl)location;
+        } else if (location == null) {
+            return UNKNOWN;
+        } else {
+            return new LocationImpl(location);
+        }
+    }
+    
+    /**
+     * Get the description of this location
+     * 
+     * @return the description (can be <code>null</code>)
+     */
+    public String getDescription() {
+        return this.description;
+    }
+    
+    /**
+     * Get the URI of this location
+     * 
+     * @return the URI (<code>null</code> if unknown).
+     */
+    public String getURI() {
+        return this.uri;
+    }
+
+    /**
+     * Get the line number of this location
+     * 
+     * @return the line number (<code>-1</code> if unknown)
+     */
+    public int getLineNumber() {
+        return this.line;
+    }
+    
+    /**
+     * Get the column number of this location
+     * 
+     * @return the column number (<code>-1</code> if unknown)
+     */
+    public int getColumnNumber() {
+        return this.column;
+    }
+    
+    /**
+     * Gets a source code snippet with the default padding
+     *
+     * @param padding The amount of lines before and after the error to include
+     */
+    public List<String> getSnippet(int padding) {
+        List<String> snippet = new ArrayList<>();
+        if (getLineNumber() > 0) {
+            try {
+                InputStream in = new URL(getURI()).openStream();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                
+                int lineno = 0;
+                int errno = getLineNumber();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    lineno++;
+                    if (lineno >= errno - padding && lineno <= errno + padding) {
+                        snippet.add(line);
+                    }
+                }
+            } catch (Exception ex) {
+                // ignoring as snippet not available isn't a big deal
+            }
+        }
+        return snippet;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Location) {
+            Location other = (Location)obj;
+            return this.line == other.getLineNumber() && this.column == other.getColumnNumber()
+                   && testEquals(this.uri, other.getURI())
+                   && testEquals(this.description, other.getDescription());
+        }
+        
+        return false;
+    }
+    
+    @Override
+    public int hashCode() {
+        int hash = line ^ column;
+        if (uri != null) hash ^= uri.hashCode();
+        if (description != null) hash ^= description.hashCode();
+        
+        return hash;
+    }
+    
+    @Override
+    public String toString() {
+        return LocationUtils.toString(this);
+    }
+    
+    /**
+     * Ensure serialized unknown location resolve to {@link Location#UNKNOWN}.
+     */
+    private Object readResolve() {
+        return this.equals(Location.UNKNOWN) ? Location.UNKNOWN : this;
+    }
+    
+    private boolean testEquals(Object object1, Object object2) {
+        if (object1 == object2) {
+            return true;
+        }
+        if ((object1 == null) || (object2 == null)) {
+            return false;
+        }
+        return object1.equals(object2);
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java
new file mode 100644
index 0000000..892d3c7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/location/LocationUtils.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.location;
+
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import org.w3c.dom.Element;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXParseException;
+
+import javax.xml.transform.SourceLocator;
+import javax.xml.transform.TransformerException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Location-related utility methods.
+ */
+public class LocationUtils {
+    
+    /**
+     * The string representation of an unknown location: "<code>[unknown location]</code>".
+     */
+    public static final String UNKNOWN_STRING = "[unknown location]";
+
+    private static List<WeakReference<LocationFinder>> finders = new ArrayList<>();
+    
+    /**
+     * An finder or object locations
+     */
+    public interface LocationFinder {
+        /**
+         * Get the location of an object
+         * @param obj the object for which to find a location
+         * @param description and optional description to be added to the object's location
+         * @return the object's location or <code>null</code> if object's class isn't handled
+         *         by this finder.
+         */
+        Location getLocation(Object obj, String description);
+    }
+
+    private LocationUtils() {
+        // Forbid instanciation
+    }
+    
+    /**
+     * Builds a string representation of a location, in the
+     * "<code><em>descripton</em> - <em>uri</em>:<em>line</em>:<em>column</em></code>"
+     * format (e.g. "<code>foo - file://path/to/file.xml:3:40</code>"). For {@link Location#UNKNOWN an unknown location}, returns
+     * {@link #UNKNOWN_STRING}.
+     * 
+     * @return the string representation
+     */
+    public static String toString(Location location) {
+        StringBuilder result = new StringBuilder();
+
+        String description = location.getDescription();
+        if (description != null) {
+            result.append(description).append(" - ");
+        }
+
+        String uri = location.getURI();
+        if (uri != null) {
+            result.append(uri).append(':').append(location.getLineNumber()).append(':').append(location.getColumnNumber());
+        } else {
+            result.append(UNKNOWN_STRING);
+        }
+        
+        return result.toString();
+    }
+
+    /**
+     * Parse a location string of the form "<code><em>uri</em>:<em>line</em>:<em>column</em></code>" (e.g.
+     * "<code>path/to/file.xml:3:40</code>") to a Location object. Additionally, a description may
+     * also optionally be present, separated with an hyphen (e.g. "<code>foo - path/to/file.xml:3.40</code>").
+     * 
+     * @param text the text to parse
+     * @return the location (possibly <code>null</code> if text was null or in an incorrect format)
+     */
+    public static LocationImpl parse(String text) throws IllegalArgumentException {
+        if (text == null || text.length() == 0) {
+            return null;
+        }
+
+        // Do we have a description?
+        String description;
+        int uriStart = text.lastIndexOf(" - "); // lastIndexOf to allow the separator to be in the description
+        if (uriStart > -1) {
+            description = text.substring(0, uriStart);
+            uriStart += 3; // strip " - "
+        } else {
+            description = null;
+            uriStart = 0;
+        }
+        
+        try {
+            int colSep = text.lastIndexOf(':');
+            if (colSep > -1) {
+                int column = Integer.parseInt(text.substring(colSep + 1));
+                
+                int lineSep = text.lastIndexOf(':', colSep - 1);
+                if (lineSep > -1) {
+                    int line = Integer.parseInt(text.substring(lineSep + 1, colSep));
+                    return new LocationImpl(description, text.substring(uriStart, lineSep), line, column);
+                }
+            } else {
+                // unkonwn?
+                if (text.endsWith(UNKNOWN_STRING)) {
+                    return LocationImpl.UNKNOWN;
+                }
+            }
+        } catch(Exception e) {
+            // Ignore: handled below
+        }
+        
+        return LocationImpl.UNKNOWN;
+    }
+
+    /**
+     * Checks if a location is known, i.e. it is not null nor equal to {@link Location#UNKNOWN}.
+     * 
+     * @param location the location to check
+     * @return <code>true</code> if the location is known
+     */
+    public static boolean isKnown(Location location) {
+        return location != null && !Location.UNKNOWN.equals(location);
+    }
+
+    /**
+     * Checks if a location is unknown, i.e. it is either null or equal to {@link Location#UNKNOWN}.
+     * 
+     * @param location the location to check
+     * @return <code>true</code> if the location is unknown
+     */
+    public static boolean isUnknown(Location location) {
+        return location == null || Location.UNKNOWN.equals(location);
+    }
+
+    /**
+     * Add a {@link LocationFinder} to the list of finders that will be queried for an object's
+     * location by {@link #getLocation(Object, String)}.
+     * <p>
+     * <b>Important:</b> LocationUtils internally stores a weak reference to the finder. This
+     * avoids creating strong links between the classloader holding this class and the finder's
+     * classloader, which can cause some weird memory leaks if the finder's classloader is to
+     * be reloaded. Therefore, you <em>have</em> to keep a strong reference to the finder in the
+     * calling code, e.g.:
+     * <pre>
+     *   private static LocationUtils.LocationFinder myFinder =
+     *       new LocationUtils.LocationFinder() {
+     *           public Location getLocation(Object obj, String desc) {
+     *               ...
+     *           }
+     *       };
+     *
+     *   static {
+     *       LocationUtils.addFinder(myFinder);
+     *   }
+     * </pre>
+     * 
+     * @param finder the location finder to add
+     */
+    public static void addFinder(LocationFinder finder) {
+        if (finder == null) {
+            return;
+        }
+
+        synchronized(LocationFinder.class) {
+            // Update a clone of the current finder list to avoid breaking
+            // any iteration occuring in another thread.
+            List<WeakReference<LocationFinder>> newFinders = new ArrayList<>(finders);
+            newFinders.add(new WeakReference<LocationFinder>(finder));
+            finders = newFinders;
+        }
+    }
+    
+    /**
+     * Get the location of an object. Some well-known located classes built in the JDK are handled
+     * by this method. Handling of other located classes can be handled by adding new location finders.
+     * 
+     * @param obj the object of which to get the location
+     * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
+     */
+    public static Location getLocation(Object obj) {
+        return getLocation(obj, null);
+    }
+    
+    /**
+     * Get the location of an object. Some well-known located classes built in the JDK are handled
+     * by this method. Handling of other located classes can be handled by adding new location finders.
+     * 
+     * @param obj the object of which to get the location
+     * @param description an optional description of the object's location, used if a Location object
+     *        has to be created.
+     * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
+     */
+    public static Location getLocation(Object obj, String description) {
+        if (obj instanceof Location) {
+            return (Location) obj;
+        }
+        
+        if (obj instanceof Locatable) {
+            return ((Locatable)obj).getLocation();
+        }
+        
+        // Check some well-known locatable exceptions
+        if (obj instanceof SAXParseException) {
+            SAXParseException spe = (SAXParseException)obj;
+            if (spe.getSystemId() != null) {
+                return new LocationImpl(description, spe.getSystemId(), spe.getLineNumber(), spe.getColumnNumber());
+            } else {
+                return Location.UNKNOWN;
+            }
+        }
+        
+        if (obj instanceof TransformerException) {
+            TransformerException ex = (TransformerException)obj;
+            SourceLocator locator = ex.getLocator();
+            if (locator != null && locator.getSystemId() != null) {
+                return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber());
+            } else {
+                return Location.UNKNOWN;
+            }
+        }
+        
+        if (obj instanceof Locator) {
+            Locator locator = (Locator)obj;
+            if (locator.getSystemId() != null) {
+                return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber());
+            } else {
+                return Location.UNKNOWN;
+            }
+        }
+        
+        if (obj instanceof Element) {
+            return LocationAttributes.getLocation((Element)obj);
+        }
+
+        List<WeakReference<LocationFinder>> currentFinders = finders; // Keep the current list
+        int size = currentFinders.size();
+        for (int i = 0; i < size; i++) {
+            WeakReference<LocationFinder> ref = currentFinders.get(i);
+            LocationFinder finder = ref.get();
+            if (finder == null) {
+                // This finder was garbage collected: update finders
+                synchronized(LocationFinder.class) {
+                    // Update a clone of the current list to avoid breaking current iterations
+                    List<WeakReference<LocationFinder>> newFinders = new ArrayList<>(finders);
+                    newFinders.remove(ref);
+                    finders = newFinders;
+                }
+            } else {
+                Location result = finder.getLocation(obj, description);
+                if (result != null) {
+                    return result;
+                }
+            }
+        }
+
+        if (obj instanceof Throwable) {
+        		Throwable t = (Throwable) obj;
+        		StackTraceElement[] stack = t.getStackTrace();
+        		if (stack != null && stack.length > 0) {
+        			StackTraceElement trace = stack[0];
+        			if (trace.getLineNumber() >= 0) {
+	        			String uri = trace.getClassName();
+	        			if (trace.getFileName() != null) {
+	        				uri = uri.replace('.','/');
+	        				uri = uri.substring(0, uri.lastIndexOf('/') + 1);
+	        				uri = uri + trace.getFileName();
+	        				URL url = ClassLoaderUtil.getResource(uri, LocationUtils.class);
+	        				if (url != null) {
+        						uri = url.toString();
+	        				}
+	        			}
+	        			if (description == null) {
+	        				StringBuilder sb = new StringBuilder();
+	        				sb.append("Class: ").append(trace.getClassName()).append("\n");
+	        				sb.append("File: ").append(trace.getFileName()).append("\n");
+	        				sb.append("Method: ").append(trace.getMethodName()).append("\n");
+	        				sb.append("Line: ").append(trace.getLineNumber());
+	        				description = sb.toString();
+	        			}
+	        			return new LocationImpl(description, uri, trace.getLineNumber(), -1);
+        			}
+        		}
+        }
+
+        return Location.UNKNOWN;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/location/package.html
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/location/package.html b/core/src/main/java/com/opensymphony/xwork2/util/location/package.html
new file mode 100644
index 0000000..840814b
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/location/package.html
@@ -0,0 +1,3 @@
+<html>
+  <body>Classes and utilities used to track location information.</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java
new file mode 100644
index 0000000..e7da798
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/Logger.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging;
+
+/**
+ * Main logger interface for logging things
+ */
+@Deprecated
+public interface Logger {
+
+    void trace(String msg, String... args);
+
+    void trace(String msg, Object... args);
+
+    void trace(String msg, Throwable ex, String... args);
+
+    boolean isTraceEnabled();
+
+    void debug(String msg, String... args);
+
+    void debug(String msg, Object... args);
+
+    void debug(String msg, Throwable ex, String... args);
+
+    boolean isDebugEnabled();
+
+    void info(String msg, String... args);
+
+    void info(String msg, Throwable ex, String... args);
+
+    boolean isInfoEnabled();
+
+    void warn(String msg, String... args);
+
+    void warn(String msg, Object... args);
+
+    void warn(String msg, Throwable ex, String... args);
+
+    boolean isWarnEnabled();
+
+    void error(String msg, String... args);
+
+    void error(String msg, Object... args);
+
+    void error(String msg, Throwable ex, String... args);
+
+    boolean isErrorEnabled();
+
+    void fatal(String msg, String... args);
+
+    void fatal(String msg, Throwable ex, String... args);
+
+    boolean isFatalEnabled();
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java
new file mode 100644
index 0000000..29b70df
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerFactory.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging;
+
+import com.opensymphony.xwork2.XWorkConstants;
+import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.util.logging.commons.CommonsLoggerFactory;
+import com.opensymphony.xwork2.util.logging.jdk.JdkLoggerFactory;
+import com.opensymphony.xwork2.util.logging.log4j2.Log4j2LoggerFactory;
+import com.opensymphony.xwork2.util.logging.slf4j.Slf4jLoggerFactory;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Creates loggers.  Static accessor will lazily try to decide on the best factory if none specified.
+ */
+@Deprecated
+public abstract class LoggerFactory {
+
+    private static final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private static LoggerFactory factory;
+
+    private static final List<LoggerClass> loggers = new LinkedList<LoggerClass>(){
+        {
+            add(new LoggerClass<CommonsLoggerFactory>("org.apache.commons.logging.LogFactory", CommonsLoggerFactory.class));
+            add(new LoggerClass<Slf4jLoggerFactory>("org.slf4j.LoggerFactory", Slf4jLoggerFactory.class));
+            add(new LoggerClass<Log4j2LoggerFactory>("org.apache.logging.log4j.LogManager", Log4j2LoggerFactory.class));
+        }
+    };
+
+    public static void setLoggerFactory(LoggerFactory factory) {
+        lock.writeLock().lock();
+        try {
+            LoggerFactory.factory = factory;
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    public static Logger getLogger(Class<?> cls) {
+        return getLoggerFactory().getLoggerImpl(cls);
+    }
+
+    public static Logger getLogger(String name) {
+        return getLoggerFactory().getLoggerImpl(name);
+    }
+
+    protected static LoggerFactory getLoggerFactory() {
+        lock.readLock().lock();
+        try {
+            if (factory != null) {
+                return factory;
+            }
+        } finally {
+            lock.readLock().unlock();
+        }
+        lock.writeLock().lock();
+        try {
+            if (factory == null) {
+                createLoggerFactory();
+            }
+            return factory;
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    private static void createLoggerFactory() {
+        String userLoggerFactory = System.getProperty(XWorkConstants.XWORK_LOGGER_FACTORY);
+        if (userLoggerFactory != null) {
+            try {
+                Class clazz = Class.forName(userLoggerFactory);
+                factory = (LoggerFactory) clazz.newInstance();
+            } catch (Exception e) {
+                throw new XWorkException("System property [" + XWorkConstants.XWORK_LOGGER_FACTORY +
+                        "] was defined as [" + userLoggerFactory + "] but there is a problem to use that LoggerFactory!", e);
+            }
+        } else {
+            factory = new JdkLoggerFactory();
+            for (LoggerClass logger : loggers) {
+                if (logger.isSupported()) {
+                    factory = logger.createInstance();
+                    break;
+                }
+            }
+        }
+    }
+
+    protected abstract Logger getLoggerImpl(Class<?> cls);
+
+    protected abstract Logger getLoggerImpl(String name);
+
+    private static class LoggerClass<T extends LoggerFactory> {
+
+        private final String loggerClazzName;
+        private final Class<T> loggerImplClazz;
+
+        public LoggerClass(String loggerClazzName, Class<T> loggerImplClazz) {
+            this.loggerClazzName = loggerClazzName;
+            this.loggerImplClazz = loggerImplClazz;
+        }
+
+        public boolean isSupported() {
+            try {
+                Class.forName(loggerClazzName);
+                return true;
+            } catch (ClassNotFoundException ignore) {
+                return false;
+            }
+        }
+
+        public LoggerFactory createInstance() {
+            try {
+                return loggerImplClazz.newInstance();
+            } catch (Exception e) {
+                throw new XWorkException(e);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java
new file mode 100644
index 0000000..a513ed2
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/LoggerUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Logging utility methods
+ */
+@Deprecated
+public class LoggerUtils {
+
+    /**
+     * Formats messages using parameters. For example, the call:
+     * 
+     * <pre>
+     * format("foo #0 #1", "bob", "joe");
+     * </pre>
+     * 
+     * will return:
+     * <pre>
+     * foo bob joe
+     * </pre>
+     * 
+     * @param msg The message
+     * @param args A list of arguments.  A maximum of 10 are supported.
+     * @return The formatted string
+     */
+    public static String format(String msg, String... args) {
+        if (msg != null && msg.length() > 0 && msg.indexOf('#') > -1) {
+            StringBuilder sb = new StringBuilder();
+            boolean isArg = false;
+            for (int x = 0; x < msg.length(); x++) {
+                char c = msg.charAt(x);
+                if (isArg) {
+                    isArg = false;
+                    if (Character.isDigit(c)) {
+                        int val = Character.getNumericValue(c);
+                        if (val >= 0 && val < args.length) {
+                            sb.append(args[val]);
+                            continue;
+                        }
+                    }
+                    sb.append('#');
+                }
+                if (c == '#') {
+                    isArg = true;
+                    continue;
+                }
+                sb.append(c);
+            }
+            
+            if (isArg) {
+                sb.append('#');
+            }
+            return sb.toString();
+        }
+        return msg;
+        
+    }
+
+    public static String format(String msg, Object[] args) {
+        List<String> strArgs = new LinkedList<String>();
+        for (Object arg : args) {
+            strArgs.add(arg != null ? arg.toString() : "(null)");
+        }
+        return format(msg, strArgs.toArray(new String[strArgs.size()]));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java
new file mode 100644
index 0000000..6ee4e0f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLogger.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging.commons;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerUtils;
+import org.apache.commons.logging.Log;
+
+/**
+ * Simple logger that delegates to commons logging
+ */
+@Deprecated
+public class CommonsLogger implements Logger {
+    
+    private Log log;
+    
+    public CommonsLogger(Log log) {
+        this.log = log;
+    }
+
+    public void error(String msg, String... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Object... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Throwable ex, String... args) {
+        log.error(LoggerUtils.format(msg, args), ex);
+    }
+
+    public void info(String msg, String... args) {
+        log.info(LoggerUtils.format(msg, args));
+    }
+
+    public void info(String msg, Throwable ex, String... args) {
+        log.info(LoggerUtils.format(msg, args), ex);
+    }
+
+    
+
+    public boolean isInfoEnabled() {
+        return log.isInfoEnabled();
+    }
+
+    public void warn(String msg, String... args) {
+        log.warn(LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Object... args) {
+        log.warn(LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Throwable ex, String... args) {
+        log.warn(LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isDebugEnabled() {
+        return log.isDebugEnabled();
+    }
+    
+    public void debug(String msg, String... args) {
+        log.debug(LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Object... args) {
+        log.debug(LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Throwable ex, String... args) {
+        log.debug(LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isTraceEnabled() {
+        return log.isTraceEnabled();
+    }
+    
+    public void trace(String msg, String... args) {
+        log.trace(LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Object... args) {
+        log.trace(LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Throwable ex, String... args) {
+        log.trace(LoggerUtils.format(msg, args), ex);
+    }
+
+
+    public void fatal(String msg, String... args) {
+        log.fatal(LoggerUtils.format(msg, args));
+    }
+
+    public void fatal(String msg, Throwable ex, String... args) {
+        log.fatal(LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isErrorEnabled() {
+        return log.isErrorEnabled();
+    }
+
+    public boolean isFatalEnabled() {
+        return log.isFatalEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return log.isWarnEnabled();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java
new file mode 100644
index 0000000..0e02293
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/commons/CommonsLoggerFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging.commons;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Creates commons-logging-backed loggers
+ */
+@Deprecated
+public class CommonsLoggerFactory extends LoggerFactory {
+
+    @Override
+    protected Logger getLoggerImpl(Class<?> cls) {
+        return new CommonsLogger(LogFactory.getLog(cls));
+    }
+    
+    @Override
+    protected Logger getLoggerImpl(String name) {
+        return new CommonsLogger(LogFactory.getLog(name));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java
new file mode 100644
index 0000000..1a11346
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLogger.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging.jdk;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerUtils;
+
+import java.util.logging.Level;
+
+/**
+ * Delegates to jdk logger.  Maps fatal to Level.SEVERE along with error.
+ */
+@Deprecated
+public class JdkLogger implements Logger {
+    
+    private java.util.logging.Logger log;
+    
+    public JdkLogger(java.util.logging.Logger log) {
+        this.log = log;
+    }
+
+    public void error(String msg, String... args) {
+        log.log(Level.SEVERE, LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Object... args) {
+        log.log(Level.SEVERE, LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Throwable ex, String... args) {
+        log.log(Level.SEVERE, LoggerUtils.format(msg, args), ex);
+    }
+    
+    public void fatal(String msg, String... args) {
+        log.log(Level.SEVERE, LoggerUtils.format(msg, args));
+    }
+
+    public void fatal(String msg, Throwable ex, String... args) {
+        log.log(Level.SEVERE, LoggerUtils.format(msg, args), ex);
+    }
+
+    public void info(String msg, String... args) {
+        log.log(Level.INFO, LoggerUtils.format(msg, args));
+    }
+
+    public void info(String msg, Throwable ex, String... args) {
+        log.log(Level.INFO, LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isInfoEnabled() {
+        return log.isLoggable(Level.INFO);
+    }
+
+    public void warn(String msg, String... args) {
+        log.log(Level.WARNING, LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Object... args) {
+        log.log(Level.WARNING, LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Throwable ex, String... args) {
+        log.log(Level.WARNING, LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isDebugEnabled() {
+        return log.isLoggable(Level.FINE);
+    }
+    
+    public void debug(String msg, String... args) {
+        log.log(Level.FINE, LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Object... args) {
+        log.log(Level.FINE, LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Throwable ex, String... args) {
+        log.log(Level.FINE, LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isTraceEnabled() {
+        return log.isLoggable(Level.FINEST);
+    }
+    
+    public void trace(String msg, String... args) {
+        log.log(Level.FINEST, LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Object... args) {
+        log.log(Level.FINEST, LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Throwable ex, String... args) {
+        log.log(Level.FINEST, LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isErrorEnabled() {
+        return log.isLoggable(Level.SEVERE);
+    }
+
+    public boolean isFatalEnabled() {
+        return log.isLoggable(Level.SEVERE);
+    }
+
+    public boolean isWarnEnabled() {
+        return log.isLoggable(Level.WARNING);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java
new file mode 100644
index 0000000..dbf719c
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/jdk/JdkLoggerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging.jdk;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * Creates jdk loggers
+ */
+@Deprecated
+public class JdkLoggerFactory extends LoggerFactory {
+
+    @Override
+    protected Logger getLoggerImpl(Class<?> cls) {
+        return new JdkLogger(java.util.logging.Logger.getLogger(cls.getName()));
+    }
+    
+    @Override
+    protected Logger getLoggerImpl(String name) {
+        return new JdkLogger(java.util.logging.Logger.getLogger(name));
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2Logger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2Logger.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2Logger.java
new file mode 100644
index 0000000..228a860
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2Logger.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging.log4j2;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerUtils;
+
+/**
+ * Simple logger that delegates to log4j2 logging
+ */
+@Deprecated
+public class Log4j2Logger implements Logger {
+
+    private org.apache.logging.log4j.Logger log;
+
+    public Log4j2Logger(org.apache.logging.log4j.Logger log) {
+        this.log = log;
+    }
+
+    public void error(String msg, String... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Object... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Throwable ex, String... args) {
+        log.error(LoggerUtils.format(msg, args), ex);
+    }
+
+    public void info(String msg, String... args) {
+        log.info(LoggerUtils.format(msg, args));
+    }
+
+    public void info(String msg, Throwable ex, String... args) {
+        log.info(LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isInfoEnabled() {
+        return log.isInfoEnabled();
+    }
+
+    public void warn(String msg, String... args) {
+        log.warn(LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Object... args) {
+        log.warn(LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Throwable ex, String... args) {
+        log.warn(LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isDebugEnabled() {
+        return log.isDebugEnabled();
+    }
+    
+    public void debug(String msg, String... args) {
+        log.debug(LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Object... args) {
+        log.debug(LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Throwable ex, String... args) {
+        log.debug(LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isTraceEnabled() {
+        return log.isTraceEnabled();
+    }
+    
+    public void trace(String msg, String... args) {
+        log.trace(LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Object... args) {
+        log.trace(LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Throwable ex, String... args) {
+        log.trace(LoggerUtils.format(msg, args), ex);
+    }
+
+
+    public void fatal(String msg, String... args) {
+        log.fatal(LoggerUtils.format(msg, args));
+    }
+
+    public void fatal(String msg, Throwable ex, String... args) {
+        log.fatal(LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isErrorEnabled() {
+        return log.isErrorEnabled();
+    }
+
+    public boolean isFatalEnabled() {
+        return log.isFatalEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return log.isWarnEnabled();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2LoggerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2LoggerFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2LoggerFactory.java
new file mode 100644
index 0000000..dd2799d
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/log4j2/Log4j2LoggerFactory.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.util.logging.log4j2;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * Creates log4j2-logging-backed loggers
+ *
+ * You can use the same to explicit tell the framework which implementation to use and don't depend on class discovery:
+ * <pre>
+ *   -Dxwork.loggerFactory=com.opensymphony.xwork2.util.logging.log4j2.Log4j2LoggerFactory
+ * </pre>
+ */
+@Deprecated
+public class Log4j2LoggerFactory extends LoggerFactory {
+
+    @Override
+    protected Logger getLoggerImpl(Class<?> cls) {
+        return new Log4j2Logger(org.apache.logging.log4j.LogManager.getLogger(cls));
+    }
+
+    @Override
+    protected Logger getLoggerImpl(String name) {
+        return new Log4j2Logger(org.apache.logging.log4j.LogManager.getLogger(name));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLogger.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLogger.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLogger.java
new file mode 100644
index 0000000..a303032
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLogger.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util.logging.slf4j;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerUtils;
+
+/**
+ * Simple logger that delegates to slf4j logging
+ */
+@Deprecated
+public class Slf4jLogger implements Logger {
+    
+    private org.slf4j.Logger log;
+    
+    public Slf4jLogger(org.slf4j.Logger log) {
+        this.log = log;
+    }
+
+    public void error(String msg, String... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Object... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void error(String msg, Throwable ex, String... args) {
+        log.error(LoggerUtils.format(msg, args), ex);
+    }
+
+    public void info(String msg, String... args) {
+        log.info(LoggerUtils.format(msg, args));
+    }
+
+    public void info(String msg, Throwable ex, String... args) {
+        log.info(LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isInfoEnabled() {
+        return log.isInfoEnabled();
+    }
+
+    public void warn(String msg, String... args) {
+        log.warn(LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Object... args) {
+        log.warn(LoggerUtils.format(msg, args));
+    }
+
+    public void warn(String msg, Throwable ex, String... args) {
+        log.warn(LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isDebugEnabled() {
+        return log.isDebugEnabled();
+    }
+    
+    public void debug(String msg, String... args) {
+        log.debug(LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Object... args) {
+        log.debug(LoggerUtils.format(msg, args));
+    }
+
+    public void debug(String msg, Throwable ex, String... args) {
+        log.debug(LoggerUtils.format(msg, args), ex);
+    }
+    
+    public boolean isTraceEnabled() {
+        return log.isTraceEnabled();
+    }
+    
+    public void trace(String msg, String... args) {
+        log.trace(LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Object... args) {
+        log.trace(LoggerUtils.format(msg, args));
+    }
+
+    public void trace(String msg, Throwable ex, String... args) {
+        log.trace(LoggerUtils.format(msg, args), ex);
+    }
+
+
+    public void fatal(String msg, String... args) {
+        log.error(LoggerUtils.format(msg, args));
+    }
+
+    public void fatal(String msg, Throwable ex, String... args) {
+        log.error(LoggerUtils.format(msg, args), ex);
+    }
+
+    public boolean isErrorEnabled() {
+        return log.isErrorEnabled();
+    }
+
+    /** Fatal is not support by Slf4j */
+    public boolean isFatalEnabled() {
+        return log.isErrorEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return log.isWarnEnabled();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLoggerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLoggerFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLoggerFactory.java
new file mode 100644
index 0000000..6e3e6d5
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/logging/slf4j/Slf4jLoggerFactory.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.util.logging.slf4j;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * Creates slf4j-logging-backed loggers
+ *
+ * You can use the same to explicit tell the framework which implementation to use and don't depend on class discovery:
+ * <pre>
+ *   -Dxwork.loggerFactory=com.opensymphony.xwork2.util.logging.slf4j.Slf4jLoggerFactory
+ * </pre>
+ */
+@Deprecated
+public class Slf4jLoggerFactory extends LoggerFactory {
+
+    @Override
+    protected Logger getLoggerImpl(Class<?> cls) {
+        return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(cls));
+    }
+
+    @Override
+    protected Logger getLoggerImpl(String name) {
+        return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/package.html
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/package.html b/core/src/main/java/com/opensymphony/xwork2/util/package.html
new file mode 100644
index 0000000..e400bf9
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/package.html
@@ -0,0 +1 @@
+<body>XWork util classes.</body>

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java b/core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java
new file mode 100644
index 0000000..7438113
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ *     * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *     * Neither the name of Atlassian Software Systems Pty Ltd nor the names of
+ * its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.opensymphony.xwork2.util.profiling;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ */
+public class ObjectProfiler {
+
+    /**
+     * Given a class, and an interface that it implements, return a proxied version of the class that implements
+     * the interface.
+     * <p/>
+     * The usual use of this is to profile methods from Factory objects:
+     * <pre>
+     * public PersistenceManager getPersistenceManager()
+     * {
+     *   return new DefaultPersistenceManager();
+     * }
+     *
+     * instead write:
+     * public PersistenceManager getPersistenceManager()
+     * {
+     *   return ObjectProfiler.getProfiledObject(PersistenceManager.class, new DefaultPersistenceManager());
+     * }
+     * </pre>
+     * <p/>
+     * A side effect of this is that you will no longer be able to downcast to DefaultPersistenceManager.  This is probably a *good* thing.
+     *
+     * @param interfaceClazz The interface to implement.
+     * @param o              The object to proxy
+     * @return A proxied object, or the input object if the interfaceClazz wasn't an interface.
+     */
+    public static Object getProfiledObject(Class interfaceClazz, Object o) {
+        //if we are not active - then do nothing
+        if (!UtilTimerStack.isActive()) {
+            return o;
+        }
+
+        //this should always be true - you shouldn't be passing something that isn't an interface
+        if (interfaceClazz.isInterface()) {
+            InvocationHandler timerHandler = new TimerInvocationHandler(o);
+            return Proxy.newProxyInstance(interfaceClazz.getClassLoader(),
+                    new Class[]{interfaceClazz}, timerHandler);
+        } else {
+            return o;
+        }
+    }
+
+    /**
+     * A profiled call {@link Method#invoke(java.lang.Object, java.lang.Object[])}. If {@link UtilTimerStack#isActive() }
+     * returns false, then no profiling is performed.
+     */
+    public static Object profiledInvoke(Method target, Object value, Object[] args) throws IllegalAccessException, InvocationTargetException {
+        //if we are not active - then do nothing
+        if (!UtilTimerStack.isActive()) {
+            return target.invoke(value, args);
+        }
+
+        String logLine = new String(getTrimmedClassName(target) + "." + target.getName() + "()");
+
+        UtilTimerStack.push(logLine);
+        try {
+            Object returnValue = target.invoke(value, args);
+
+            //if the return value is an interface then we should also proxy it!
+            if (returnValue != null && target.getReturnType().isInterface()) {
+                InvocationHandler timerHandler = new TimerInvocationHandler(returnValue);
+                return Proxy.newProxyInstance(returnValue.getClass().getClassLoader(),
+                        new Class[]{target.getReturnType()}, timerHandler);
+            } else {
+                return returnValue;
+            }
+        } finally {
+            UtilTimerStack.pop(logLine);
+        }
+    }
+
+    /**
+     * Given a method, get the Method name, with no package information.
+     */
+    public static String getTrimmedClassName(Method method) {
+        String classname = method.getDeclaringClass().getName();
+        return classname.substring(classname.lastIndexOf('.') + 1);
+    }
+
+}
+
+class TimerInvocationHandler implements InvocationHandler {
+    protected Object target;
+
+    public TimerInvocationHandler(Object target) {
+        if (target == null) {
+            throw new IllegalArgumentException("Target Object passed to timer cannot be null");
+        }
+        this.target = target;
+    }
+
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        return ObjectProfiler.profiledInvoke(method, target, args);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java b/core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java
new file mode 100644
index 0000000..a475c62
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ *     * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *     * Neither the name of Atlassian Software Systems Pty Ltd nor the names of
+ * its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.opensymphony.xwork2.util.profiling;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Bean to contain information about the pages profiled
+ *
+ * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ * @version $Date$ $Id$
+ */
+public class ProfilingTimerBean implements java.io.Serializable {
+
+    private static final long serialVersionUID = -6180672043920208784L;
+
+    List<ProfilingTimerBean> children = new ArrayList<>();
+    ProfilingTimerBean parent = null;
+
+    String resource;
+
+    long startTime;
+    long totalTime;
+
+    public ProfilingTimerBean(String resource) {
+        this.resource = resource;
+    }
+
+    protected void addParent(ProfilingTimerBean parent) {
+        this.parent = parent;
+    }
+
+    public ProfilingTimerBean getParent() {
+        return parent;
+    }
+
+
+    public void addChild(ProfilingTimerBean child) {
+        children.add(child);
+        child.addParent(this);
+    }
+
+
+    public void setStartTime() {
+        this.startTime = System.currentTimeMillis();
+    }
+
+    public void setEndTime() {
+        this.totalTime = System.currentTimeMillis() - startTime;
+    }
+
+    public String getResource() {
+        return resource;
+    }
+
+    /**
+     * Get a formatted string representing all the methods that took longer than a specified time.
+     */
+
+    public String getPrintable(long minTime) {
+        return getPrintable("", minTime);
+    }
+
+    protected String getPrintable(String indent, long minTime) {
+        //only print the value if we are larger or equal to the min time.
+        if (totalTime >= minTime) {
+            StringBuilder buffer = new StringBuilder();
+            buffer.append(indent);
+            buffer.append("[" + totalTime + "ms] - " + resource);
+            buffer.append("\n");
+
+            for (ProfilingTimerBean aChildren : children) {
+                buffer.append((aChildren).getPrintable(indent + "  ", minTime));
+            }
+
+            return buffer.toString();
+        } else
+            return "";
+    }
+}
+