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 43F0E18F22 for ; Wed, 17 Jun 2015 21:09:18 +0000 (UTC) Received: (qmail 77172 invoked by uid 500); 17 Jun 2015 21:09:04 -0000 Delivered-To: apmail-struts-commits-archive@struts.apache.org Received: (qmail 76777 invoked by uid 500); 17 Jun 2015 21:09:03 -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 75226 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 8F8E1DFFC0; 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:37 -0000 Message-Id: <461e9287770c418ca8b7b0758d3e8d46@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [37/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/ognl/accessor/XWorkEnumerationAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkEnumerationAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkEnumerationAccessor.java new file mode 100644 index 0000000..84745e4 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkEnumerationAccessor.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.ognl.accessor; + +import ognl.EnumerationPropertyAccessor; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + + +/** + * @author plightbo + */ +public class XWorkEnumerationAccessor extends EnumerationPropertyAccessor { + + ObjectPropertyAccessor opa = new ObjectPropertyAccessor(); + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + opa.setProperty(context, target, name, value); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.java new file mode 100644 index 0000000..2a6184b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkIteratorPropertyAccessor.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.ognl.accessor; + +import ognl.IteratorPropertyAccessor; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + + +/** + * @author plightbo + */ +public class XWorkIteratorPropertyAccessor extends IteratorPropertyAccessor { + + ObjectPropertyAccessor opa = new ObjectPropertyAccessor(); + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + opa.setProperty(context, target, name, value); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java new file mode 100644 index 0000000..6201dae --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java @@ -0,0 +1,177 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ListPropertyAccessor; +import ognl.OgnlException; +import ognl.PropertyAccessor; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Overrides the list property accessor so in the case of trying + * to add properties of a given bean and the JavaBean is not present, + * this class will create the necessary blank JavaBeans. + * + * @author Gabriel Zimmerman + */ +public class XWorkListPropertyAccessor extends ListPropertyAccessor { + + private XWorkCollectionPropertyAccessor _sAcc = new XWorkCollectionPropertyAccessor(); + + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + private OgnlUtil ognlUtil; + + @Inject("java.util.Collection") + public void setXWorkCollectionPropertyAccessor(PropertyAccessor acc) { + this._sAcc = (XWorkCollectionPropertyAccessor) acc; + } + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { + this.objectTypeDeterminer = ot; + } + + @Inject + public void setOgnlUtil(OgnlUtil util) { + this.ognlUtil = util; + } + + @Override + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + + if (ReflectionContextState.isGettingByKeyProperty(context) + || name.equals(XWorkCollectionPropertyAccessor.KEY_PROPERTY_FOR_CREATION)) { + return _sAcc.getProperty(context, target, name); + } else if (name instanceof String) { + return super.getProperty(context, target, name); + } + ReflectionContextState.updateCurrentPropertyPath(context, name); + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + + if (name instanceof Number + && ReflectionContextState.isCreatingNullObjects(context) + && objectTypeDeterminer.shouldCreateIfNew(lastClass,lastProperty,target,null,true)) { + + List list = (List) target; + int index = ((Number) name).intValue(); + int listSize = list.size(); + + if (lastClass == null || lastProperty == null) { + return super.getProperty(context, target, name); + } + Class beanClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, name); + if (listSize <= index) { + Object result; + + for (int i = listSize; i < index; i++) { + list.add(null); + } + try { + list.add(index, result = objectFactory.buildBean(beanClass, context)); + } catch (Exception exc) { + throw new XWorkException(exc); + } + return result; + } else if (list.get(index) == null) { + Object result; + try { + list.set(index, result = objectFactory.buildBean(beanClass, context)); + } catch (Exception exc) { + throw new XWorkException(exc); + } + return result; + } + } + return super.getProperty(context, target, name); + } + + @Override + public void setProperty(Map context, Object target, Object name, Object value) + throws OgnlException { + + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + Class convertToClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, name); + + if (name instanceof String && value.getClass().isArray()) { + // looks like the input game in the form of "someList.foo" and + // we are expected to define the index values ourselves. + // So let's do it: + + Collection c = (Collection) target; + Object[] values = (Object[]) value; + for (Object v : values) { + try { + Object o = objectFactory.buildBean(convertToClass, context); + ognlUtil.setValue((String) name, context, o, v); + c.add(o); + } catch (Exception e) { + throw new OgnlException("Error converting given String values for Collection.", e); + } + } + + // we don't want to do the normal list property setting now, since we've already done the work + // just return instead + return; + } + + Object realValue = getRealValue(context, value, convertToClass); + + if (target instanceof List && name instanceof Number) { + //make sure there are enough spaces in the List to set + List list = (List) target; + int listSize = list.size(); + int count = ((Number) name).intValue(); + if (count >= listSize) { + for (int i = listSize; i <= count; i++) { + list.add(null); + } + } + } + + super.setProperty(context, target, name, realValue); + } + + private Object getRealValue(Map context, Object value, Class convertToClass) { + if (value == null || convertToClass == null) { + return value; + } + return xworkConverter.convertValue(context, value, convertToClass); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java new file mode 100644 index 0000000..e30f07e --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessor.java @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.MapPropertyAccessor; +import ognl.OgnlException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; + +/** + * Implementation of PropertyAccessor that sets and gets properties by storing and looking + * up values in Maps. + * + * @author Gabriel Zimmerman + */ +public class XWorkMapPropertyAccessor extends MapPropertyAccessor { + + private static final Logger LOG = LogManager.getLogger(XWorkMapPropertyAccessor.class); + + private static final String[] INDEX_ACCESS_PROPS = new String[]{"size", "isEmpty", "keys", "values"}; + + private XWorkConverter xworkConverter; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + + @Inject + public void setXWorkConverter(XWorkConverter conv) { + this.xworkConverter = conv; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { + this.objectTypeDeterminer = ot; + } + + @Override + public Object getProperty(Map context, Object target, Object name) throws OgnlException { + LOG.trace("Entering getProperty ({},{},{})", context, target, name); + + ReflectionContextState.updateCurrentPropertyPath(context, name); + // if this is one of the regular index access + // properties then just let the superclass deal with the + // get. + if (name instanceof String && contains(INDEX_ACCESS_PROPS, (String) name)) { + return super.getProperty(context, target, name); + } + + Object result = null; + + try{ + result = super.getProperty(context, target, name); + } catch (ClassCastException ex) { + } + + if (result == null) { + //find the key class and convert the name to that class + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + if (lastClass == null || lastProperty == null) { + return null; + } + Object key = getKey(context, name); + Map map = (Map) target; + result = map.get(key); + + if (result == null && + Boolean.TRUE.equals(context.get(ReflectionContextState.CREATE_NULL_OBJECTS)) + && objectTypeDeterminer.shouldCreateIfNew(lastClass,lastProperty,target,null,false)) { + Class valueClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, key); + + try { + result = objectFactory.buildBean(valueClass, context); + map.put(key, result); + } catch (Exception exc) { + } + } + } + return result; + } + + /** + * @param array + * @param name + */ + private boolean contains(String[] array, String name) { + for (String anArray : array) { + if (anArray.equals(name)) { + return true; + } + } + + return false; + } + + @Override + public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { + LOG.trace("Entering setProperty({},{},{},{})", context, target, name, value); + + Object key = getKey(context, name); + Map map = (Map) target; + map.put(key, getValue(context, value)); + } + + private Object getValue(Map context, Object value) { + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + if (lastClass == null || lastProperty == null) { + return value; + } + Class elementClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, null); + if (elementClass == null) { + return value; // nothing is specified, we assume it will be the value passed in. + } + return xworkConverter.convertValue(context, value, elementClass); + } + + private Object getKey(Map context, Object name) { + Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); + String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); + if (lastClass == null || lastProperty == null) { + // return java.lang.String.class; + // commented out the above -- it makes absolutely no sense for when setting basic maps! + return name; + } + Class keyClass = objectTypeDeterminer.getKeyClass(lastClass, lastProperty); + if (keyClass == null) { + keyClass = java.lang.String.class; + } + + return xworkConverter.convertValue(context, name, keyClass); + } +} + http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessorTest.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessorTest.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessorTest.java new file mode 100644 index 0000000..a746c7e --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMapPropertyAccessorTest.java @@ -0,0 +1,54 @@ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.util.Element; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; + +import java.util.Collections; +import java.util.Map; + +public class XWorkMapPropertyAccessorTest extends XWorkTestCase { + public void testCreateNullObjectsIsFalseByDefault() { + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.push(new MapHolder(Collections.emptyMap())); + assertNull(vs.findValue("map[key]")); + } + + public void testMapContentsAreReturned() { + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.push(new MapHolder(Collections.singletonMap("key", "value"))); + assertEquals("value", vs.findValue("map['key']")); + } + + public void testNullIsNotReturnedWhenCreateNullObjectsIsSpecified() { + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.push(new MapHolder(Collections.emptyMap())); + ReflectionContextState.setCreatingNullObjects(vs.getContext(), true); + + Object value = vs.findValue("map['key']"); + assertNotNull(value); + assertSame(Object.class, value.getClass()); + } + + public void testNullIsReturnedWhenCreateNullObjectsIsSpecifiedAsFalse() { + ValueStack vs = ActionContext.getContext().getValueStack(); + vs.push(new MapHolder(Collections.emptyMap())); + ReflectionContextState.setCreatingNullObjects(vs.getContext(), false); + assertNull(vs.findValue("map['key']")); + } + + private static class MapHolder { + private final Map map; + + public MapHolder(Map m) { + this.map = m; + } + + @Element(value = Object.class) + public Map getMap() { + return map; + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java new file mode 100644 index 0000000..7a05bc5 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkMethodAccessor.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.beans.PropertyDescriptor; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + + +/** + * Allows methods to be executed under normal cirumstances, except when {@link ReflectionContextState#DENY_METHOD_EXECUTION} + * is in the action context with a value of true. + * + * @author Patrick Lightbody + * @author tmjee + */ +public class XWorkMethodAccessor extends ObjectMethodAccessor { + + private static final Logger LOG = LogManager.getLogger(XWorkMethodAccessor.class); + + /** + * @deprecated Use {@link ReflectionContextState#DENY_METHOD_EXECUTION} instead + */ + @Deprecated public static final String DENY_METHOD_EXECUTION = ReflectionContextState.DENY_METHOD_EXECUTION; + /** + * @deprecated Use {@link ReflectionContextState#DENY_INDEXED_ACCESS_EXECUTION} instead + */ + @Deprecated public static final String DENY_INDEXED_ACCESS_EXECUTION = ReflectionContextState.DENY_INDEXED_ACCESS_EXECUTION; + + + @Override + public Object callMethod(Map context, Object object, String string, Object[] objects) throws MethodFailedException { + + //Collection property accessing + //this if statement ensures that ognl + //statements of the form someBean.mySet('keyPropVal') + //return the set element with value of the keyProp given + + if (objects.length == 1 && context instanceof OgnlContext) { + try { + OgnlContext ogContext=(OgnlContext)context; + if (OgnlRuntime.hasSetProperty(ogContext, object, string)) { + PropertyDescriptor descriptor=OgnlRuntime.getPropertyDescriptor(object.getClass(), string); + Class propertyType=descriptor.getPropertyType(); + if ((Collection.class).isAssignableFrom(propertyType)) { + //go directly through OgnlRuntime here + //so that property strings are not cleared + //i.e. OgnlUtil should be used initially, OgnlRuntime + //thereafter + + Object propVal=OgnlRuntime.getProperty(ogContext, object, string); + //use the Collection property accessor instead of the individual property accessor, because + //in the case of Lists otherwise the index property could be used + PropertyAccessor accessor=OgnlRuntime.getPropertyAccessor(Collection.class); + ReflectionContextState.setGettingByKeyProperty(ogContext,true); + return accessor.getProperty(ogContext,propVal,objects[0]); + } + } + } catch (Exception oe) { + //this exception should theoretically never happen + //log it + LOG.error("An unexpected exception occurred", oe); + } + + } + + //HACK - we pass indexed method access i.e. setXXX(A,B) pattern + if ((objects.length == 2 && string.startsWith("set")) || (objects.length == 1 && string.startsWith("get"))) { + Boolean exec = (Boolean) context.get(ReflectionContextState.DENY_INDEXED_ACCESS_EXECUTION); + boolean e = ((exec == null) ? false : exec.booleanValue()); + if (!e) { + return callMethodWithDebugInfo(context, object, string, objects); + } + } + Boolean exec = (Boolean) context.get(ReflectionContextState.DENY_METHOD_EXECUTION); + boolean e = ((exec == null) ? false : exec.booleanValue()); + + if (!e) { + return callMethodWithDebugInfo(context, object, string, objects); + } else { + return null; + } + } + + private Object callMethodWithDebugInfo(Map context, Object object, String methodName, Object[] objects) throws MethodFailedException { + try { + return super.callMethod(context, object, methodName, objects); + } + catch(MethodFailedException e) { + if (LOG.isDebugEnabled()) { + if (!(e.getReason() instanceof NoSuchMethodException)) { + // the method exists on the target object, but something went wrong + LOG.debug("Error calling method through OGNL: object: [{}] method: [{}] args: [{}]", e.getReason(), object.toString(), methodName, Arrays.toString(objects)); + } + } + throw e; + } + } + + @Override + public Object callStaticMethod(Map context, Class aClass, String string, Object[] objects) throws MethodFailedException { + Boolean exec = (Boolean) context.get(ReflectionContextState.DENY_METHOD_EXECUTION); + boolean e = ((exec == null) ? false : exec.booleanValue()); + + if (!e) { + return callStaticMethodWithDebugInfo(context, aClass, string, objects); + } else { + return null; + } + } + + private Object callStaticMethodWithDebugInfo(Map context, Class aClass, String methodName, + Object[] objects) throws MethodFailedException { + try { + return super.callStaticMethod(context, aClass, methodName, objects); + } + catch(MethodFailedException e) { + if (LOG.isDebugEnabled()) { + if (!(e.getReason() instanceof NoSuchMethodException)) { + // the method exists on the target class, but something went wrong + LOG.debug("Error calling method through OGNL, class: [{}] method: [{}] args: [{}]", e.getReason(), aClass.getName(), methodName, Arrays.toString(objects)); + } + } + throw e; + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.java new file mode 100644 index 0000000..5351401 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkObjectPropertyAccessor.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.opensymphony.xwork2.ognl.accessor; + +import com.opensymphony.xwork2.conversion.impl.XWorkConverter; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import ognl.ObjectPropertyAccessor; +import ognl.OgnlException; + +import java.util.Map; + +/** + * @author Gabe + */ +public class XWorkObjectPropertyAccessor extends ObjectPropertyAccessor { + @Override + public Object getProperty(Map context, Object target, Object oname) + throws OgnlException { + //set the last set objects in the context + //so if the next objects accessed are + //Maps or Collections they can use the information + //to determine conversion types + context.put(XWorkConverter.LAST_BEAN_CLASS_ACCESSED, target.getClass()); + context.put(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED, oname.toString()); + ReflectionContextState.updateCurrentPropertyPath(context, oname); + return super.getProperty(context, target, oname); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/package.html ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/package.html b/core/src/main/java/com/opensymphony/xwork2/package.html new file mode 100644 index 0000000..3794154 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/package.html @@ -0,0 +1 @@ +Main XWork interfaces and classes. http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/result/ParamNameAwareResult.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/result/ParamNameAwareResult.java b/core/src/main/java/com/opensymphony/xwork2/result/ParamNameAwareResult.java new file mode 100644 index 0000000..1067984 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/result/ParamNameAwareResult.java @@ -0,0 +1,10 @@ +package com.opensymphony.xwork2.result; + +/** + * Accept parameter name/value to be set on {@link com.opensymphony.xwork2.Result} + */ +public interface ParamNameAwareResult { + + boolean acceptableParameterName(String name, String value); + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java new file mode 100644 index 0000000..64592fa --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java @@ -0,0 +1,82 @@ +package com.opensymphony.xwork2.security; + +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Used across different interceptors to check if given string matches one of the excluded patterns. + */ +public interface AcceptedPatternsChecker { + + /** + * Checks if value matches any of patterns on exclude list + * + * @param value to check + * @return object containing result of matched pattern and pattern itself + */ + public IsAccepted isAccepted(String value); + + /** + * Sets excluded patterns during runtime + * + * @param commaDelimitedPatterns comma delimited string with patterns + */ + public void setAcceptedPatterns(String commaDelimitedPatterns); + + /** + * Set excluded patterns during runtime + * + * @param patterns array of additional excluded patterns + */ + public void setAcceptedPatterns(String[] patterns); + + /** + * Sets excluded patterns during runtime + * + * @param patterns set of additional patterns + */ + public void setAcceptedPatterns(Set patterns); + + /** + * Allow access list of all defined excluded patterns + * + * @return set of excluded patterns + */ + public Set getAcceptedPatterns(); + + public final static class IsAccepted { + + private final boolean accepted; + private final String acceptedPattern; + + public static IsAccepted yes(String acceptedPattern) { + return new IsAccepted(true, acceptedPattern); + } + + public static IsAccepted no(String acceptedPatterns) { + return new IsAccepted(false, acceptedPatterns); + } + + private IsAccepted(boolean accepted, String acceptedPattern) { + this.accepted = accepted; + this.acceptedPattern = acceptedPattern; + } + + public boolean isAccepted() { + return accepted; + } + + public String getAcceptedPattern() { + return acceptedPattern; + } + + @Override + public String toString() { + return "IsAccepted {" + + "accepted=" + accepted + + ", acceptedPattern=" + acceptedPattern + + " }"; + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsChecker.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsChecker.java new file mode 100644 index 0000000..00e9f79 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsChecker.java @@ -0,0 +1,76 @@ +package com.opensymphony.xwork2.security; + +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker { + + private static final Logger LOG = LogManager.getLogger(DefaultAcceptedPatternsChecker.class); + + public static final String[] ACCEPTED_PATTERNS = { + "\\w+((\\.\\w+)|(\\[\\d+\\])|(\\(\\d+\\))|(\\['(\\w|[\\u4e00-\\u9fa5])+'\\])|(\\('(\\w|[\\u4e00-\\u9fa5])+'\\)))*" + }; + + private Set acceptedPatterns; + + public DefaultAcceptedPatternsChecker() { + setAcceptedPatterns(ACCEPTED_PATTERNS); + } + + @Inject(value = XWorkConstants.OVERRIDE_ACCEPTED_PATTERNS, required = false) + public void setOverrideAcceptedPatterns(String acceptablePatterns) { + LOG.warn("Overriding accepted patterns [{}] with [{}], be aware that this affects all instances and safety of your application!", + XWorkConstants.OVERRIDE_ACCEPTED_PATTERNS, acceptablePatterns); + acceptedPatterns = new HashSet<>(); + for (String pattern : TextParseUtil.commaDelimitedStringToSet(acceptablePatterns)) { + acceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); + } + } + + @Inject(value = XWorkConstants.ADDITIONAL_ACCEPTED_PATTERNS, required = false) + public void setAdditionalAcceptedPatterns(String acceptablePatterns) { + LOG.warn("Adding additional global patterns [{}] to accepted patterns!", acceptablePatterns); + for (String pattern : TextParseUtil.commaDelimitedStringToSet(acceptablePatterns)) { + acceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); + } + } + + public void setAcceptedPatterns(String commaDelimitedPatterns) { + setAcceptedPatterns(TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns)); + } + + public void setAcceptedPatterns(String[] additionalPatterns) { + setAcceptedPatterns(new HashSet<>(Arrays.asList(additionalPatterns))); + } + + public void setAcceptedPatterns(Set patterns) { + LOG.trace("Sets accepted patterns [{}]", patterns); + acceptedPatterns = new HashSet<>(patterns.size()); + for (String pattern : patterns) { + acceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); + } + } + + public IsAccepted isAccepted(String value) { + for (Pattern acceptedPattern : acceptedPatterns) { + if (acceptedPattern.matcher(value).matches()) { + LOG.trace("[{}] matches accepted pattern [{}]", value, acceptedPattern); + return IsAccepted.yes(acceptedPattern.toString()); + } + } + return IsAccepted.no(acceptedPatterns.toString()); + } + + public Set getAcceptedPatterns() { + return acceptedPatterns; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsChecker.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsChecker.java new file mode 100644 index 0000000..f6d48cd --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsChecker.java @@ -0,0 +1,77 @@ +package com.opensymphony.xwork2.security; + +import com.opensymphony.xwork2.XWorkConstants; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class DefaultExcludedPatternsChecker implements ExcludedPatternsChecker { + + private static final Logger LOG = LogManager.getLogger(DefaultExcludedPatternsChecker.class); + + public static final String[] EXCLUDED_PATTERNS = { + "(^|.*#)(dojo|struts|session|request|application|servlet(Request|Response)|parameters|context|_memberAccess)(\\.|\\[).*", + "^(action|method):.*" + }; + + private Set excludedPatterns; + + public DefaultExcludedPatternsChecker() { + setExcludedPatterns(EXCLUDED_PATTERNS); + } + + @Inject(value = XWorkConstants.OVERRIDE_EXCLUDED_PATTERNS, required = false) + public void setOverrideExcludePatterns(String excludePatterns) { + LOG.warn("Overriding excluded patterns [{}] with [{}], be aware that this affects all instances and safety of your application!", + XWorkConstants.OVERRIDE_EXCLUDED_PATTERNS, excludePatterns); + excludedPatterns = new HashSet(); + for (String pattern : TextParseUtil.commaDelimitedStringToSet(excludePatterns)) { + excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); + } + } + + @Inject(value = XWorkConstants.ADDITIONAL_EXCLUDED_PATTERNS, required = false) + public void setAdditionalExcludePatterns(String excludePatterns) { + LOG.debug("Adding additional global patterns [{}] to excluded patterns!", excludePatterns); + for (String pattern : TextParseUtil.commaDelimitedStringToSet(excludePatterns)) { + excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); + } + } + + public void setExcludedPatterns(String commaDelimitedPatterns) { + setExcludedPatterns(TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns)); + } + + public void setExcludedPatterns(String[] patterns) { + setExcludedPatterns(new HashSet<>(Arrays.asList(patterns))); + } + + public void setExcludedPatterns(Set patterns) { + LOG.trace("Sets excluded patterns [{}]", patterns); + excludedPatterns = new HashSet<>(patterns.size()); + for (String pattern : patterns) { + excludedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); + } + } + + public IsExcluded isExcluded(String value) { + for (Pattern excludedPattern : excludedPatterns) { + if (excludedPattern.matcher(value).matches()) { + LOG.trace("[{}] matches excluded pattern [{}]", value, excludedPattern); + return IsExcluded.yes(excludedPattern); + } + } + return IsExcluded.no(excludedPatterns); + } + + public Set getExcludedPatterns() { + return excludedPatterns; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/security/ExcludedPatternsChecker.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/security/ExcludedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/ExcludedPatternsChecker.java new file mode 100644 index 0000000..5a3bc07 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/security/ExcludedPatternsChecker.java @@ -0,0 +1,82 @@ +package com.opensymphony.xwork2.security; + +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Used across different interceptors to check if given string matches one of the excluded patterns. + */ +public interface ExcludedPatternsChecker { + + /** + * Checks if value matches any of patterns on exclude list + * + * @param value to check + * @return object containing result of matched pattern and pattern itself + */ + public IsExcluded isExcluded(String value); + + /** + * Sets excluded patterns during runtime + * + * @param commaDelimitedPatterns comma delimited string with patterns + */ + public void setExcludedPatterns(String commaDelimitedPatterns); + + /** + * Sets excluded patterns during runtime + * + * @param patterns array of additional excluded patterns + */ + public void setExcludedPatterns(String[] patterns); + + /** + * Sets excluded patterns during runtime + * + * @param patterns set of additional patterns + */ + public void setExcludedPatterns(Set patterns); + + /** + * Allow access list of all defined excluded patterns + * + * @return set of excluded patterns + */ + public Set getExcludedPatterns(); + + public final static class IsExcluded { + + private final boolean excluded; + private final String excludedPattern; + + public static IsExcluded yes(Pattern excludedPattern) { + return new IsExcluded(true, excludedPattern.pattern()); + } + + public static IsExcluded no(Set excludedPatterns) { + return new IsExcluded(false, excludedPatterns.toString()); + } + + private IsExcluded(boolean excluded, String excludedPattern) { + this.excluded = excluded; + this.excludedPattern = excludedPattern; + } + + public boolean isExcluded() { + return excluded; + } + + public String getExcludedPattern() { + return excludedPattern; + } + + @Override + public String toString() { + return "IsExcluded { " + + "excluded=" + excluded + + ", excludedPattern=" + excludedPattern + + " }"; + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java b/core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java new file mode 100644 index 0000000..f1137d9 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/spring/SpringObjectFactory.java @@ -0,0 +1,287 @@ +/* + * 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.spring; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.inject.Inject; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * Simple implementation of the ObjectFactory that makes use of Spring's application context if one has been configured, + * before falling back on the default mechanism of instantiating a new class using the class name.

In order to use + * this class in your application, you will need to instantiate a copy of this class and set it as XWork's ObjectFactory + * before the xwork.xml file is parsed. In a servlet environment, this could be done using a ServletContextListener. + * + * @author Simon Stewart (sms@lateral.net) + */ +public class SpringObjectFactory extends ObjectFactory implements ApplicationContextAware { + private static final Logger LOG = LogManager.getLogger(SpringObjectFactory.class); + + protected ApplicationContext appContext; + protected AutowireCapableBeanFactory autoWiringFactory; + protected int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; + private final Map classes = new HashMap<>(); + private boolean useClassCache = true; + private boolean alwaysRespectAutowireStrategy = false; + /** + * This is temporary solution, after validating can be removed + * @since 2.3.18 + */ + private boolean enableAopSupport = false; + + @Inject(value="applicationContextPath",required=false) + public void setApplicationContextPath(String ctx) { + if (ctx != null) { + setApplicationContext(new ClassPathXmlApplicationContext(ctx)); + } + } + + @Inject(value = "enableAopSupport", required = false) + public void setEnableAopSupport(String enableAopSupport) { + this.enableAopSupport = BooleanUtils.toBoolean(enableAopSupport); + } + + /** + * Set the Spring ApplicationContext that should be used to look beans up with. + * + * @param appContext The Spring ApplicationContext that should be used to look beans up with. + */ + public void setApplicationContext(ApplicationContext appContext) throws BeansException { + this.appContext = appContext; + autoWiringFactory = findAutoWiringBeanFactory(this.appContext); + } + + /** + * Sets the autowiring strategy + * + * @param autowireStrategy + */ + public void setAutowireStrategy(int autowireStrategy) { + switch (autowireStrategy) { + case AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT: + LOG.info("Setting autowire strategy to autodetect"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_BY_NAME: + LOG.info("Setting autowire strategy to name"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE: + LOG.info("Setting autowire strategy to type"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR: + LOG.info("Setting autowire strategy to constructor"); + this.autowireStrategy = autowireStrategy; + break; + case AutowireCapableBeanFactory.AUTOWIRE_NO: + LOG.info("Setting autowire strategy to none"); + this.autowireStrategy = autowireStrategy; + break; + default: + throw new IllegalStateException("Invalid autowire type set"); + } + } + + public int getAutowireStrategy() { + return autowireStrategy; + } + + + /** + * If the given context is assignable to AutowireCapbleBeanFactory or contains a parent or a factory that is, then + * set the autoWiringFactory appropriately. + * + * @param context + */ + protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) { + if (context instanceof AutowireCapableBeanFactory) { + // Check the context + return (AutowireCapableBeanFactory) context; + } else if (context instanceof ConfigurableApplicationContext) { + // Try and grab the beanFactory + return ((ConfigurableApplicationContext) context).getBeanFactory(); + } else if (context.getParent() != null) { + // And if all else fails, try again with the parent context + return findAutoWiringBeanFactory(context.getParent()); + } + return null; + } + + /** + * Looks up beans using Spring's application context before falling back to the method defined in the {@link + * ObjectFactory}. + * + * @param beanName The name of the bean to look up in the application context + * @param extraContext + * @return A bean from Spring or the result of calling the overridden + * method. + * @throws Exception + */ + @Override + public Object buildBean(String beanName, Map extraContext, boolean injectInternal) throws Exception { + Object o; + + if (appContext.containsBean(beanName)) { + o = appContext.getBean(beanName); + } else { + Class beanClazz = getClassInstance(beanName); + o = buildBean(beanClazz, extraContext); + } + if (injectInternal) { + injectInternalBeans(o); + } + return o; + } + + /** + * @param clazz + * @param extraContext + * @throws Exception + */ + @Override + public Object buildBean(Class clazz, Map extraContext) throws Exception { + Object bean; + + try { + // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies + if (alwaysRespectAutowireStrategy) { + // Leave the creation up to Spring + bean = autoWiringFactory.createBean(clazz, autowireStrategy, false); + injectApplicationContext(bean); + return injectInternalBeans(bean); + } else if (enableAopSupport) { + bean = autoWiringFactory.createBean(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); + bean = autoWireBean(bean, autoWiringFactory); + bean = autoWiringFactory.initializeBean(bean, bean.getClass().getName()); + return bean; + } else { + bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); + bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName()); + bean = autoWiringFactory.initializeBean(bean, bean.getClass().getName()); + bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName()); + return autoWireBean(bean, autoWiringFactory); + } + } catch (UnsatisfiedDependencyException e) { + LOG.error("Error building bean", e); + // Fall back + return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory); + } + } + + public Object autoWireBean(Object bean) { + return autoWireBean(bean, autoWiringFactory); + } + + /** + * @param bean + * @param autoWiringFactory + */ + public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) { + if (autoWiringFactory != null) { + autoWiringFactory.autowireBeanProperties(bean, autowireStrategy, false); + } + injectApplicationContext(bean); + + injectInternalBeans(bean); + + return bean; + } + + private void injectApplicationContext(Object bean) { + if (bean instanceof ApplicationContextAware) { + ((ApplicationContextAware) bean).setApplicationContext(appContext); + } + } + + public Class getClassInstance(String className) throws ClassNotFoundException { + Class clazz = null; + if (useClassCache) { + synchronized(classes) { + // this cache of classes is needed because Spring sucks at dealing with situations where the + // class instance changes + clazz = (Class) classes.get(className); + } + } + + if (clazz == null) { + if (appContext.containsBean(className)) { + clazz = appContext.getBean(className).getClass(); + } else { + clazz = super.getClassInstance(className); + } + + if (useClassCache) { + synchronized(classes) { + classes.put(className, clazz); + } + } + } + + return clazz; + } + + /** + * This method sets the ObjectFactory used by XWork to this object. It's best used as the "init-method" of a Spring + * bean definition in order to hook Spring and XWork together properly (as an alternative to the + * org.apache.struts2.spring.lifecycle.SpringObjectFactoryListener) + * @deprecated Since 2.1 as it isn't necessary + */ + @Deprecated public void initObjectFactory() { + // not necessary anymore + } + + /** + * Allows for ObjectFactory implementations that support + * Actions without no-arg constructors. + * + * @return false + */ + @Override + public boolean isNoArgConstructorRequired() { + return false; + } + + /** + * Enable / disable caching of classes loaded by Spring. + * + * @param useClassCache + */ + public void setUseClassCache(boolean useClassCache) { + this.useClassCache = useClassCache; + } + + /** + * Determines if the autowire strategy is always followed when creating beans + * + * @param alwaysRespectAutowireStrategy True if the strategy is always used + */ + public void setAlwaysRespectAutowireStrategy(boolean alwaysRespectAutowireStrategy) { + this.alwaysRespectAutowireStrategy = alwaysRespectAutowireStrategy; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java b/core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java new file mode 100644 index 0000000..f249f5f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/spring/SpringProxyableObjectFactory.java @@ -0,0 +1,87 @@ +/* + * 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.spring; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * SpringProxyableObjectFactory. + * + * @author Jason Carreira + */ +public class SpringProxyableObjectFactory extends SpringObjectFactory { + + private static final Logger LOG = LogManager.getLogger(SpringProxyableObjectFactory.class); + + private List skipBeanNames = new ArrayList<>(); + + @Override + public Object buildBean(String beanName, Map extraContext) throws Exception { + LOG.debug("Building bean for name {}", beanName); + if (!skipBeanNames.contains(beanName)) { + ApplicationContext anAppContext = getApplicationContext(extraContext); + try { + LOG.debug("Trying the application context... appContext = {},\n bean name = {}", anAppContext, beanName); + return anAppContext.getBean(beanName); + } catch (NoSuchBeanDefinitionException e) { + LOG.debug("Did not find bean definition for bean named {}, creating a new one...", beanName); + if (autoWiringFactory instanceof BeanDefinitionRegistry) { + try { + Class clazz = Class.forName(beanName); + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) autoWiringFactory; + RootBeanDefinition def = new RootBeanDefinition(clazz, autowireStrategy, true); + def.setScope(BeanDefinition.SCOPE_SINGLETON); + LOG.debug("Registering a new bean definition for class {}", beanName); + registry.registerBeanDefinition(beanName,def); + try { + return anAppContext.getBean(beanName); + } catch (NoSuchBeanDefinitionException e2) { + LOG.warn("Could not register new bean definition for bean {}", beanName); + skipBeanNames.add(beanName); + } + } catch (ClassNotFoundException e1) { + skipBeanNames.add(beanName); + } + } + } + } + LOG.debug("Returning autowired instance created by default ObjectFactory"); + return autoWireBean(super.buildBean(beanName, extraContext), autoWiringFactory); + } + + /** + * Subclasses may override this to return a different application context. + * Note that this application context should see any changes made to the + * autoWiringFactory, so the application context should be either + * the original or a child context of the original. + * + * @param context provided context. + */ + protected ApplicationContext getApplicationContext(Map context) { + return appContext; + } +} + http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java new file mode 100644 index 0000000..fe02ca5 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/ActionAutowiringInterceptor.java @@ -0,0 +1,136 @@ +/* + * 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.spring.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.spring.SpringObjectFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.web.context.WebApplicationContext; + +/** + * + * TODO: Give a description of the Interceptor. + * + * + * + * TODO: Describe the paramters for this Interceptor. + * + * + * + * TODO: Discuss some possible extension of the Interceptor. + * + * + *

+ * 
+ * <!-- TODO: Describe how the Interceptor reference will effect execution -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ *      TODO: fill in the interceptor reference.
+ *     <interceptor-ref name=""/>
+ *     <result name="success">good_result.ftl</result>
+ * </action>
+ * 
+ * 
+ * + * Autowires action classes to Spring beans. The strategy for autowiring the beans can be configured + * by setting the parameter on the interceptor. Actions that need access to the ActionContext + * can implements the ApplicationContextAware interface. The context will also be placed on + * the action context under the APPLICATION_CONTEXT attribute. + * + * @author Simon Stewart + * @author Eric Hauser + */ +public class ActionAutowiringInterceptor extends AbstractInterceptor implements ApplicationContextAware { + private static final Logger LOG = LogManager.getLogger(ActionAutowiringInterceptor.class); + + public static final String APPLICATION_CONTEXT = "com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor.applicationContext"; + + private boolean initialized = false; + private ApplicationContext context; + private SpringObjectFactory factory; + private Integer autowireStrategy; + + /** + * @param autowireStrategy + */ + public void setAutowireStrategy(Integer autowireStrategy) { + this.autowireStrategy = autowireStrategy; + } + + /** + * Looks for the ApplicationContext under the attribute that the Spring listener sets in + * the servlet context. The configuration is done the first time here instead of in init() since the + * ActionContext is not available during Interceptor initialization. + *

+ * Autowires the action to Spring beans and places the ApplicationContext + * on the ActionContext + *

+ * TODO Should this check to see if the SpringObjectFactory has already been configured + * instead of instantiating a new one? Or is there a good reason for the interceptor to have it's own + * factory? + * + * @param invocation + * @throws Exception + */ + @Override public String intercept(ActionInvocation invocation) throws Exception { + if (!initialized) { + ApplicationContext applicationContext = (ApplicationContext) ActionContext.getContext().getApplication().get( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + + if (applicationContext == null) { + LOG.warn("ApplicationContext could not be found. Action classes will not be autowired."); + } else { + setApplicationContext(applicationContext); + factory = new SpringObjectFactory(); + factory.setApplicationContext(getApplicationContext()); + if (autowireStrategy != null) { + factory.setAutowireStrategy(autowireStrategy.intValue()); + } + } + initialized = true; + } + + if (factory != null) { + Object bean = invocation.getAction(); + factory.autoWireBean(bean); + + ActionContext.getContext().put(APPLICATION_CONTEXT, context); + } + return invocation.invoke(); + } + + /** + * @param applicationContext + * @throws BeansException + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } + + /** + * @return context + */ + protected ApplicationContext getApplicationContext() { + return context; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html b/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html new file mode 100644 index 0000000..6579e80 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/spring/interceptor/package.html @@ -0,0 +1 @@ +Spring specific interceptor classes. http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/spring/package.html ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/spring/package.html b/core/src/main/java/com/opensymphony/xwork2/spring/package.html new file mode 100644 index 0000000..91d2d95 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/spring/package.html @@ -0,0 +1 @@ +Spring ObjectFactory classes. http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java new file mode 100644 index 0000000..309db06 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/test/StubConfigurationProvider.java @@ -0,0 +1,36 @@ +package com.opensymphony.xwork2.test; + +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.util.location.LocatableProperties; + +public class StubConfigurationProvider implements ConfigurationProvider { + + public void destroy() { + // TODO Auto-generated method stub + + } + + public void init(Configuration configuration) throws ConfigurationException { + // TODO Auto-generated method stub + } + + public void loadPackages() throws ConfigurationException { + // TODO Auto-generated method stub + + } + + public boolean needsReload() { + // TODO Auto-generated method stub + return false; + } + + public void register(ContainerBuilder builder, LocatableProperties props) + throws ConfigurationException { + // TODO Auto-generated method stub + + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java new file mode 100644 index 0000000..08cae03 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java @@ -0,0 +1,199 @@ +/* + * 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; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * AnnotationUtils + * + * Various utility methods dealing with annotations + * + * @author Rainer Hermanns + * @author Zsolt Szasz, zsolt at lorecraft dot com + * @author Dan Oxlade, dan d0t oxlade at gmail d0t c0m + * @version $Id$ + */ +public class AnnotationUtils { + + private static final Pattern SETTER_PATTERN = Pattern.compile("set([A-Z][A-Za-z0-9]*)$"); + private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is|has)([A-Z][A-Za-z0-9]*)$"); + + + + /** + * Adds all fields with the specified Annotation of class clazz and its superclasses to allFields + * + * @param annotationClass + * @param clazz + * @param allFields + */ + public static void addAllFields(Class annotationClass, Class clazz, List allFields) { + + if (clazz == null) { + return; + } + + Field[] fields = clazz.getDeclaredFields(); + + for (Field field : fields) { + Annotation ann = field.getAnnotation(annotationClass); + if (ann!=null) { + allFields.add(field); + } + } + addAllFields(annotationClass, clazz.getSuperclass(), allFields); + } + + /** + * Adds all methods with the specified Annotation of class clazz and its superclasses to allFields + * + * @param annotationClass + * @param clazz + * @param allMethods + */ + public static void addAllMethods(Class annotationClass, Class clazz, List allMethods) { + + if (clazz == null) { + return; + } + + Method[] methods = clazz.getDeclaredMethods(); + + for (Method method : methods) { + Annotation ann = method.getAnnotation(annotationClass); + if (ann!=null) { + allMethods.add(method); + } + } + addAllMethods(annotationClass, clazz.getSuperclass(), allMethods); + } + + /** + * + * @param clazz + * @param allInterfaces + */ + public static void addAllInterfaces(Class clazz, List allInterfaces) { + if (clazz == null) { + return; + } + + Class[] interfaces = clazz.getInterfaces(); + allInterfaces.addAll(Arrays.asList(interfaces)); + addAllInterfaces(clazz.getSuperclass(), allInterfaces); + } + + /** + * For the given Class get a collection of the the {@link AnnotatedElement}s + * that match the given annotations or if no annotations are + * specified then return all of the annotated elements of the given Class. + * Includes only the method level annotations. + * + * @param clazz The {@link Class} to inspect + * @param annotation the {@link Annotation}s to find + * @return A {@link Collection}<{@link AnnotatedElement}> containing all of the + * method {@link AnnotatedElement}s matching the specified {@link Annotation}s + */ + public static Collection getAnnotatedMethods(Class clazz, Class... annotation){ + Collection toReturn = new HashSet<>(); + + for (Method m : clazz.getMethods()) { + if (org.apache.commons.lang3.ArrayUtils.isNotEmpty(annotation) && isAnnotatedBy(m, annotation)) { + toReturn.add(m); + } else if (org.apache.commons.lang3.ArrayUtils.isEmpty(annotation) && org.apache.commons.lang3.ArrayUtils.isNotEmpty(m.getAnnotations())) { + toReturn.add(m); + } + } + + return toReturn; + } + + /** + * Varargs version of AnnotatedElement.isAnnotationPresent() + * @see AnnotatedElement + */ + public static boolean isAnnotatedBy(AnnotatedElement annotatedElement, Class... annotation) { + if (org.apache.commons.lang3.ArrayUtils.isEmpty(annotation)) { + return false; + } + + for( Class c : annotation ){ + if( annotatedElement.isAnnotationPresent(c) ) return true; + } + + return false; + } + + /** + * Returns the property name for a method. + * This method is independent from property fields. + * + * @param method The method to get the property name for. + * @return the property name for given method; null if non could be resolved. + */ + public static String resolvePropertyName(Method method) { + + Matcher matcher = SETTER_PATTERN.matcher(method.getName()); + if (matcher.matches() && method.getParameterTypes().length == 1) { + String raw = matcher.group(1); + return raw.substring(0, 1).toLowerCase() + raw.substring(1); + } + + matcher = GETTER_PATTERN.matcher(method.getName()); + if (matcher.matches() && method.getParameterTypes().length == 0) { + String raw = matcher.group(2); + return raw.substring(0, 1).toLowerCase() + raw.substring(1); + } + + return null; + } + + /** + * Returns the annotation on the given class or the package of the class. This searchs up the + * class hierarchy and the package hierarchy for the closest match. + * + * @param clazz The class to search for the annotation. + * @param annotationClass The Class of the annotation. + * @return The annotation or null. + */ + public static T findAnnotation(Class clazz, Class annotationClass) { + T ann = clazz.getAnnotation(annotationClass); + while (ann == null && clazz != null) { + ann = clazz.getAnnotation(annotationClass); + if (ann == null) { + ann = clazz.getPackage().getAnnotation(annotationClass); + } + if (ann == null) { + clazz = clazz.getSuperclass(); + if (clazz != null) { + ann = clazz.getAnnotation(annotationClass); + } + } + } + + return ann; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.java new file mode 100644 index 0000000..99f4ab6 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/ArrayUtils.java @@ -0,0 +1,56 @@ +/* + * 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; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Dan Oxlade, dan d0t oxlade at gmail d0t c0m + * @deprecated Can be replaced eith ArrayUtils from lang3 package --> org.apache.commons.lang3.ArrayUtils + */ +@Deprecated +public class ArrayUtils { + + public static boolean isEmpty(Object[] array) { + return null == array || array.length == 0; + } + + public static boolean isNotEmpty(Object[] array) { + return !isEmpty(array); + } + + /** + * Return a collection from the comma delimited String. + * + * @param commaDelim the comma delimited String. + * @return A collection from the comma delimited String. Returns null if the string is empty. + */ + public static Collection asCollection(String commaDelim) { + if (commaDelim == null || commaDelim.trim().length() == 0) { + return null; + } + return TextParseUtil.commaDelimitedStringToSet(commaDelim); + } + + public static Set asSet(T... element) { + HashSet elements = new HashSet(element.length); + Collections.addAll(elements, element); + return elements; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java new file mode 100644 index 0000000..919f5af --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/ClassLoaderUtil.java @@ -0,0 +1,251 @@ +/* + * Copyright 2002-2003,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; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; + + +/** + * This class is extremely useful for loading resources and classes in a fault tolerant manner + * that works across different applications servers. + *

+ * It has come out of many months of frustrating use of multiple application servers at Atlassian, + * please don't change things unless you're sure they're not going to break in one server or another! + * + */ +public class ClassLoaderUtil { + + /** + * Load all resources with a given name, potentially aggregating all results + * from the searched classloaders. If no results are found, the resource name + * is prepended by '/' and tried again. + * + * This method will try to load the resources using the following methods (in order): + *

    + *
  • From Thread.currentThread().getContextClassLoader() + *
  • From ClassLoaderUtil.class.getClassLoader() + *
  • callingClass.getClassLoader() + *
+ * + * @param resourceName The name of the resources to load + * @param callingClass The Class object of the calling object + */ + public static Iterator getResources(String resourceName, Class callingClass, boolean aggregate) throws IOException { + + AggregateIterator iterator = new AggregateIterator<>(); + + iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName)); + + if (!iterator.hasNext() || aggregate) { + iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName)); + } + + if (!iterator.hasNext() || aggregate) { + ClassLoader cl = callingClass.getClassLoader(); + + if (cl != null) { + iterator.addEnumeration(cl.getResources(resourceName)); + } + } + + if (!iterator.hasNext() && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) { + return getResources('/' + resourceName, callingClass, aggregate); + } + + return iterator; + } + + /** + * Load a given resource. + *

+ * This method will try to load the resource using the following methods (in order): + *

    + *
  • From {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()} + *
  • From {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()} + *
  • From the {@link Class#getClassLoader() callingClass.getClassLoader() } + *
+ * + * @param resourceName The name of the resource to load + * @param callingClass The Class object of the calling object + */ + public static URL getResource(String resourceName, Class callingClass) { + URL url = Thread.currentThread().getContextClassLoader().getResource(resourceName); + + if (url == null) { + url = ClassLoaderUtil.class.getClassLoader().getResource(resourceName); + } + + if (url == null) { + ClassLoader cl = callingClass.getClassLoader(); + + if (cl != null) { + url = cl.getResource(resourceName); + } + } + + if ((url == null) && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) { + return getResource('/' + resourceName, callingClass); + } + + return url; + } + + /** + * This is a convenience method to load a resource as a stream. + * + * The algorithm used to find the resource is given in getResource() + * + * @param resourceName The name of the resource to load + * @param callingClass The Class object of the calling object + */ + public static InputStream getResourceAsStream(String resourceName, Class callingClass) { + URL url = getResource(resourceName, callingClass); + + try { + return (url != null) ? url.openStream() : null; + } catch (IOException e) { + return null; + } + } + + /** + * Load a class with a given name. + *

+ * It will try to load the class in the following order: + *

    + *
  • From {@link Thread#getContextClassLoader() Thread.currentThread().getContextClassLoader()} + *
  • Using the basic {@link Class#forName(java.lang.String) } + *
  • From {@link Class#getClassLoader() ClassLoaderUtil.class.getClassLoader()} + *
  • From the {@link Class#getClassLoader() callingClass.getClassLoader() } + *
+ * + * @param className The name of the class to load + * @param callingClass The Class object of the calling object + * @throws ClassNotFoundException If the class cannot be found anywhere. + */ + public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException { + try { + return Thread.currentThread().getContextClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + try { + return ClassLoaderUtil.class.getClassLoader().loadClass(className); + } catch (ClassNotFoundException exc) { + return callingClass.getClassLoader().loadClass(className); + } + } + } + } + + /** + * Prints the current classloader hierarchy - useful for debugging. + */ + public static void printClassLoader() { + System.out.println("ClassLoaderUtils.printClassLoader"); + printClassLoader(Thread.currentThread().getContextClassLoader()); + } + + /** + * Prints the classloader hierarchy from a given classloader - useful for debugging. + */ + public static void printClassLoader(ClassLoader cl) { + System.out.println("ClassLoaderUtils.printClassLoader(cl = " + cl + ")"); + + if (cl != null) { + printClassLoader(cl.getParent()); + } + } + + + + /** + * Aggregates Enumeration instances into one iterator and filters out duplicates. Always keeps one + * ahead of the enumerator to protect against returning duplicates. + */ + static class AggregateIterator implements Iterator { + + LinkedList> enums = new LinkedList<>(); + Enumeration cur = null; + E next = null; + Set loaded = new HashSet(); + + public AggregateIterator addEnumeration(Enumeration e) { + if (e.hasMoreElements()) { + if (cur == null) { + cur = e; + next = e.nextElement(); + loaded.add(next); + } else { + enums.add(e); + } + } + return this; + } + + public boolean hasNext() { + return (next != null); + } + + public E next() { + if (next != null) { + E prev = next; + next = loadNext(); + return prev; + } else { + throw new NoSuchElementException(); + } + } + + private Enumeration determineCurrentEnumeration() { + if (cur != null && !cur.hasMoreElements()) { + if (enums.size() > 0) { + cur = enums.removeLast(); + } else { + cur = null; + } + } + return cur; + } + + private E loadNext() { + if (determineCurrentEnumeration() != null) { + E tmp = cur.nextElement(); + int loadedSize = loaded.size(); + while (loaded.contains(tmp)) { + tmp = loadNext(); + if (tmp == null || loaded.size() > loadedSize) { + break; + } + } + if (tmp != null) { + loaded.add(tmp); + } + return tmp; + } + return null; + + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +}