SHIRO-501 - Add support for String interpolation If the optional dependency 'commons-configuration2' is on the classpath String interpolation will be available. String prefixes 'sys', 'env' and 'const' which provide system properties, environment variables, and field constants respectively.
Project: http://git-wip-us.apache.org/repos/asf/shiro/repo Commit: http://git-wip-us.apache.org/repos/asf/shiro/commit/af75fb58 Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/af75fb58 Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/af75fb58 Branch: refs/heads/master Commit: af75fb584ef6981c3c5263d00a396ec5f900716c Parents: f9fb8b9 Author: Brian Demers <bdem...@apache.org> Authored: Mon Oct 3 12:22:30 2016 -0400 Committer: Brian Demers <bdem...@apache.org> Committed: Tue Oct 18 12:46:13 2016 -0400 ---------------------------------------------------------------------- config/ogdl/pom.xml | 6 + .../shiro/config/CommonsInterpolator.java | 72 +++++++++ .../shiro/config/DefaultInterpolator.java | 39 +++++ .../org/apache/shiro/config/Interpolator.java | 35 +++++ .../apache/shiro/config/ReflectionBuilder.java | 17 ++- .../shiro/config/CommonsInterpolatorTest.groovy | 57 +++++++ .../shiro/config/DefaultInterpolatorTest.groovy | 48 ++++++ .../shiro/config/ReflectionBuilderTest.groovy | 148 +++++++++++++++++++ .../org/apache/shiro/config/SimpleBean.groovy | 1 + pom.xml | 14 ++ 10 files changed, 436 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/pom.xml ---------------------------------------------------------------------- diff --git a/config/ogdl/pom.xml b/config/ogdl/pom.xml index ad85a21..cdfa47e 100644 --- a/config/ogdl/pom.xml +++ b/config/ogdl/pom.xml @@ -68,6 +68,12 @@ <artifactId>commons-beanutils</artifactId> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-configuration2</artifactId> + <optional>true</optional> + </dependency> + <!-- Test dependencies: --> <dependency> <groupId>org.slf4j</groupId> http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/main/java/org/apache/shiro/config/CommonsInterpolator.java ---------------------------------------------------------------------- diff --git a/config/ogdl/src/main/java/org/apache/shiro/config/CommonsInterpolator.java b/config/ogdl/src/main/java/org/apache/shiro/config/CommonsInterpolator.java new file mode 100644 index 0000000..0d1c7fc --- /dev/null +++ b/config/ogdl/src/main/java/org/apache/shiro/config/CommonsInterpolator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.shiro.config; + +import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; +import org.apache.commons.configuration2.interpol.ConstantLookup; +import org.apache.commons.configuration2.interpol.EnvironmentLookup; +import org.apache.commons.configuration2.interpol.SystemPropertiesLookup; + +/** + * Commons-Config interpolation wrapper. This implementation uses a {@link ConfigurationInterpolator} with the default + * lookup: <code>sys</code> (system properties), <code>env</code> (environment variables>, and <code>const</code> (constants). + * + * <table> + * <tr> + * <th>lookup</th> + * <th>example</th> + * <th>value</th> + * </tr> + * <tr> + * <td>sys</td> + * <td>${sys:os.name}</td> + * <td>mac os x</td> + * </tr> + * <tr> + * <td>env</td> + * <td>${env:EDITOR}</td> + * <td>vi</td> + * </tr> + * <tr> + * <td>const</td> + * <td>${const:java.awt.event.KeyEvent.VK_ENTER}</td> + * <td>\n</td> + * </tr> + * </table> + * + * @see ConfigurationInterpolator + * @since 1.4 + */ +public class CommonsInterpolator implements Interpolator { + + final private ConfigurationInterpolator interpolator; + + public CommonsInterpolator() { + this.interpolator = new ConfigurationInterpolator(); + + interpolator.registerLookup("const", new ConstantLookup()); + interpolator.addDefaultLookup(new SystemPropertiesLookup()); + interpolator.addDefaultLookup(new EnvironmentLookup()); + } + + @Override + public String interpolate(String value) { + return (String) interpolator.interpolate(value); + } +} http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/main/java/org/apache/shiro/config/DefaultInterpolator.java ---------------------------------------------------------------------- diff --git a/config/ogdl/src/main/java/org/apache/shiro/config/DefaultInterpolator.java b/config/ogdl/src/main/java/org/apache/shiro/config/DefaultInterpolator.java new file mode 100644 index 0000000..8addd2f --- /dev/null +++ b/config/ogdl/src/main/java/org/apache/shiro/config/DefaultInterpolator.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.shiro.config; + +/** + * This {@link Interpolator} simply returns the original value. This is implementation is useful when interpolation + * is not desired. + * + * @since 1.4 + */ +public class DefaultInterpolator implements Interpolator { + + /** + * Simply returns the original <code>value</code>. + * + * @param value value to be interpolated. + * @return Simply returns the original <code>value</code>. + */ + @Override + public String interpolate(String value) { + return value; + } +} http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/main/java/org/apache/shiro/config/Interpolator.java ---------------------------------------------------------------------- diff --git a/config/ogdl/src/main/java/org/apache/shiro/config/Interpolator.java b/config/ogdl/src/main/java/org/apache/shiro/config/Interpolator.java new file mode 100644 index 0000000..0de933e --- /dev/null +++ b/config/ogdl/src/main/java/org/apache/shiro/config/Interpolator.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.shiro.config; + +/** + * Basic String interpolation interface. Typically implementations will use the Maven/Ant like notation: ${key}, but + * This is up to the implementation. + * + * @since 1.4 + */ +public interface Interpolator { + + /** + * Interpolates <code>value</code> and returns the result. + * @param value the source text + * @return the String result of the interpolation, or <code>value</code>, if there was not change. + */ + String interpolate(String value); +} http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/main/java/org/apache/shiro/config/ReflectionBuilder.java ---------------------------------------------------------------------- diff --git a/config/ogdl/src/main/java/org/apache/shiro/config/ReflectionBuilder.java b/config/ogdl/src/main/java/org/apache/shiro/config/ReflectionBuilder.java index 22a05f7..af4fa06 100644 --- a/config/ogdl/src/main/java/org/apache/shiro/config/ReflectionBuilder.java +++ b/config/ogdl/src/main/java/org/apache/shiro/config/ReflectionBuilder.java @@ -83,6 +83,8 @@ public class ReflectionBuilder { private static final String EVENT_BUS_NAME = "eventBus"; + private final Interpolator interpolator; + private final Map<String, Object> objects; /** * @since 1.3 @@ -110,6 +112,9 @@ public class ReflectionBuilder { } public ReflectionBuilder(Map<String, ?> defaults) { + + this.interpolator = createInterpolator(); + this.objects = createDefaultObjectMap(); this.registeredEventSubscribers = new LinkedHashMap<String,Object>(); apply(defaults); @@ -247,7 +252,8 @@ public class ReflectionBuilder { for (Map.Entry<String, String> entry : kvPairs.entrySet()) { String lhs = entry.getKey(); - String rhs = entry.getValue(); + String rhs = (String) interpolator.interpolate(entry.getValue()); +// String rhs = entry.getValue(); String beanId = parseBeanId(lhs); if (beanId != null) { //a beanId could be parsed, so the line is a bean instance definition @@ -719,6 +725,15 @@ public class ReflectionBuilder { applyProperty(object, propertyName, value); } + private Interpolator createInterpolator() { + + if (ClassUtils.isAvailable("org.apache.commons.configuration2.interpol.ConfigurationInterpolator")) { + return new CommonsInterpolator(); + } + + return new DefaultInterpolator(); + } + private class BeanConfigurationProcessor { private final List<Statement> statements = new ArrayList<Statement>(); http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy ---------------------------------------------------------------------- diff --git a/config/ogdl/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy b/config/ogdl/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy new file mode 100644 index 0000000..851aeb0 --- /dev/null +++ b/config/ogdl/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.shiro.config + +import org.junit.Test + +import static org.junit.Assert.* + +/** + * Tests for {@link CommonsInterpolator}. + * @since 1.4 + */ +class CommonsInterpolatorTest { + + @SuppressWarnings("unused") + public final static String TEST_ME = "success"; + + @Test + void testBasicOperation() { + + def interpolator = new CommonsInterpolator(); + + assertNull interpolator.interpolate(null); + + def sourceString = """ + \${os.name} + \${foobar} + \${const:org.apache.shiro.config.CommonsInterpolatorTest.TEST_ME} + Some other text + """ + + def expectedResult = """ + ${System.getProperty("os.name")} + \${foobar} + success + Some other text + """.toString() + + assertEquals expectedResult, interpolator.interpolate(sourceString) + } +} http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy ---------------------------------------------------------------------- diff --git a/config/ogdl/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy b/config/ogdl/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy new file mode 100644 index 0000000..4456068 --- /dev/null +++ b/config/ogdl/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.shiro.config + +import org.junit.Test + +import static org.junit.Assert.* + +/** + * Tests for {@link DefaultInterpolator}. + * @since 1.4 + */ +public class DefaultInterpolatorTest { + + @Test + void testBasicOperation() { + + def interpolator = new DefaultInterpolator(); + + assertNull interpolator.interpolate(null); + + def sourceString = """ + \${sys:os.name} + \${foobar} + \${env:HOSTTYPE} + Some other text + """ + + assertSame sourceString, interpolator.interpolate(sourceString) + } + +} http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy ---------------------------------------------------------------------- diff --git a/config/ogdl/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy b/config/ogdl/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy index ffeb318..5b12d4c 100644 --- a/config/ogdl/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy +++ b/config/ogdl/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy @@ -21,10 +21,12 @@ package org.apache.shiro.config import org.apache.shiro.codec.Base64 import org.apache.shiro.codec.CodecSupport import org.apache.shiro.codec.Hex +import org.apache.shiro.config.event.BeanEvent import org.apache.shiro.util.CollectionUtils import org.junit.Test import static org.junit.Assert.* +import static org.hamcrest.Matchers.* /** * Unit tests for the {@link ReflectionBuilder} implementation. @@ -524,4 +526,150 @@ class ReflectionBuilderTest { assertEquals(5, bean.getIntProp()); assertEquals("someString", bean.getStringProp()); } + + @Test + void testBeanListeners() { + + def ini = new Ini(); + ini.load ''' + loggingListener = org.apache.shiro.config.event.LoggingBeanEventListener + listenerOne = org.apache.shiro.config.RecordingBeanListener + listenerTwo = org.apache.shiro.config.RecordingBeanListener + + simpleBeanFactory = org.apache.shiro.config.SimpleBeanFactory + simpleBeanFactory.factoryInt = 5 + simpleBeanFactory.factoryString = someString + + compositeBean = org.apache.shiro.config.CompositeBean + compositeBean.simpleBean = $simpleBeanFactory + ''' + + ReflectionBuilder builder = new ReflectionBuilder(); + Map<String, ?> objects = builder.buildObjects(ini.getSections().iterator().next()); + assertFalse(CollectionUtils.isEmpty(objects)); + + assertInstantiatedEvents("listenerOne", objects, 4) //3 beans following + its own instantiated event + assertConfiguredEvents("listenerOne", objects, 4) //3 beans following + its own configured event + assertInitializedEvents("listenerOne", objects, 4) //3 beans following + its own initialized event + + assertInstantiatedEvents("listenerTwo", objects, 3) //2 beans following + its own instantiated event + assertConfiguredEvents("listenerTwo", objects, 3); //2 beans following + its own configured event + assertInitializedEvents("listenerTwo", objects, 3); //2 beans following + its own initialized event + + builder.destroy(); + + assertDestroyedEvents("listenerOne", objects, 4); //3 beans defined after it + its own destroyed event + assertDestroyedEvents("listenerTwo", objects, 3); //2 beans defined after it + its own destroyed event + } + + + /** + * @since 1.4 + */ + @Test + void testSimpleInterpolation() { + + Map<String, String> defs = new LinkedHashMap<String, String>(); + defs.put("simpleBeanFactory", "org.apache.shiro.config.SimpleBeanFactory"); + defs.put("simpleBeanFactory.factoryInt", "5"); + defs.put("simpleBeanFactory.factoryString", "\${os.name}"); + defs.put("compositeBean", "org.apache.shiro.config.CompositeBean"); + defs.put("compositeBean.simpleBean", '$simpleBeanFactory'); + + ReflectionBuilder builder = new ReflectionBuilder(); + Map objects = builder.buildObjects(defs); + assertFalse(CollectionUtils.isEmpty(objects)); + CompositeBean compositeBean = (CompositeBean) objects.get("compositeBean"); + SimpleBean bean = compositeBean.getSimpleBean(); + assertNotNull(bean); + assertEquals(5, bean.getIntProp()); + assertEquals(System.getProperty("os.name"), bean.getStringProp()); + + } + + /** + * @since 1.4 + */ + @Test + void testInterpolationForMapKeysAndLists() { + + // using os.name and os.arch because they are available on every system + + Map<String, String> defs = new LinkedHashMap<String, String>(); + defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean"); + defs.put("simpleBean1.stringList", "\${os.name}, \${os.arch}"); + defs.put("simpleBean2", "org.apache.shiro.config.SimpleBean"); + defs.put("compositeBean", "org.apache.shiro.config.CompositeBean"); + defs.put("compositeBean.simpleBeanMap", '\${os.name}:$simpleBean1, two:$simpleBean2'); + + ReflectionBuilder builder = new ReflectionBuilder(); + Map objects = builder.buildObjects(defs); + assertFalse(CollectionUtils.isEmpty(objects)); + + CompositeBean compositeBean = (CompositeBean) objects.get("compositeBean"); + assertNotNull(compositeBean); + + def beanMap = compositeBean.getSimpleBeanMap() + assertNotNull(beanMap) + assertThat beanMap, allOf(hasKey(System.getProperty("os.name")), hasKey("two"), aMapWithSize(2)) + + def beanOne = beanMap.get(System.getProperty("os.name")) + assertThat beanOne.stringList, allOf(hasItem(System.getProperty("os.name")), hasItem(System.getProperty("os.arch")), hasSize(2)) + + assertNotNull(beanMap.get("two")) + } + + void assertInstantiatedEvents(String name, Map<String, ?> objects, int expected) { + def bean = objects.get(name) as RecordingBeanListener + def events = bean.getInstantiatedEvents() + assertEquals(expected, events.size()) + + checkType(name, events, "simpleBeanFactory", SimpleBeanFactory); + checkType(name, events, "compositeBean", CompositeBean); + } + + void assertConfiguredEvents(String name, Map<String, ?> objects, int expected) { + def bean = objects.get(name) as RecordingBeanListener + def events = bean.getConfiguredEvents(); + assertEquals(expected, events.size()) + + checkType(name, events, "listenerTwo", RecordingBeanListener); + checkType(name, events, "simpleBeanFactory", SimpleBeanFactory); + checkType(name, events, "compositeBean", CompositeBean); + } + + void assertInitializedEvents(String name, Map<String, ?> objects, int expected) { + def bean = objects.get(name) as RecordingBeanListener + def events = bean.getInitializedEvents(); + assertEquals(expected, events.size()) + + checkType(name, events, "listenerTwo", RecordingBeanListener); + checkType(name, events, "simpleBeanFactory", SimpleBeanFactory); + checkType(name, events, "compositeBean", CompositeBean); + } + + void assertDestroyedEvents(String name, Map<String, ?> objects, int expected) { + def bean = objects.get(name) as RecordingBeanListener + def events = bean.getDestroyedEvents(); + assertEquals(expected, events.size()) + + if (expected > 3) { + checkType(name, events, "listenerOne", RecordingBeanListener); + } + checkType(name, events, "listenerTwo", RecordingBeanListener); + checkType(name, events, "simpleBeanFactory", SimpleBeanFactory); + checkType(name, events, "compositeBean", CompositeBean); + } + + void checkType(String instanceName, List<? extends BeanEvent> events, String name, Class<?> expectedType) { + for(BeanEvent event: events) { + if(event.getBeanName().equals(name)) { + assertTrue("Notification for bean " + name + " did not provide an instance of " + expectedType + + " to listener " + instanceName, + expectedType.isInstance(event.getBean())) + return; + } + } + fail("No bean named " + name + " was ever notified to listener " + instanceName + "."); + } } http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/config/ogdl/src/test/groovy/org/apache/shiro/config/SimpleBean.groovy ---------------------------------------------------------------------- diff --git a/config/ogdl/src/test/groovy/org/apache/shiro/config/SimpleBean.groovy b/config/ogdl/src/test/groovy/org/apache/shiro/config/SimpleBean.groovy index 49b21bd..244ef48 100644 --- a/config/ogdl/src/test/groovy/org/apache/shiro/config/SimpleBean.groovy +++ b/config/ogdl/src/test/groovy/org/apache/shiro/config/SimpleBean.groovy @@ -25,6 +25,7 @@ class SimpleBean { int intProp; byte[] byteArrayProp; List<SimpleBean> simpleBeans; + List<String> stringList; Map<String,Object> mapProp = new LinkedHashMap<String,Object>(); public SimpleBean(){} http://git-wip-us.apache.org/repos/asf/shiro/blob/af75fb58/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index d1b41e3..ac78c2b 100644 --- a/pom.xml +++ b/pom.xml @@ -747,6 +747,20 @@ <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> + + <dependency> + <!-- optional dep for the reflection builder --> + <groupId>org.apache.commons</groupId> + <artifactId>commons-configuration2</artifactId> + <version>2.1</version> + <exclusions> + <exclusion> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> <groupId>org.openid4java</groupId> <artifactId>openid4java-consumer</artifactId>