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/f48a8de8
Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/f48a8de8
Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/f48a8de8

Branch: refs/heads/1.4.x
Commit: f48a8de807c8a42433585fdc2cbc544b244c33d2
Parents: d890bb3
Author: Brian Demers <bdem...@apache.org>
Authored: Mon Oct 3 12:22:30 2016 -0400
Committer: Brian Demers <bdem...@apache.org>
Committed: Fri Oct 14 15:15:51 2016 -0400

----------------------------------------------------------------------
 core/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   | 58 ++++++++++++++++
 .../org/apache/shiro/config/SimpleBean.java     |  9 +++
 pom.xml                                         | 14 ++++
 10 files changed, 354 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/shiro/blob/f48a8de8/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index 79f67fa..df61785 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -80,6 +80,12 @@
         </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/f48a8de8/core/src/main/java/org/apache/shiro/config/CommonsInterpolator.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/shiro/config/CommonsInterpolator.java 
b/core/src/main/java/org/apache/shiro/config/CommonsInterpolator.java
new file mode 100644
index 0000000..0d1c7fc
--- /dev/null
+++ b/core/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/f48a8de8/core/src/main/java/org/apache/shiro/config/DefaultInterpolator.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/shiro/config/DefaultInterpolator.java 
b/core/src/main/java/org/apache/shiro/config/DefaultInterpolator.java
new file mode 100644
index 0000000..8addd2f
--- /dev/null
+++ b/core/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/f48a8de8/core/src/main/java/org/apache/shiro/config/Interpolator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/Interpolator.java 
b/core/src/main/java/org/apache/shiro/config/Interpolator.java
new file mode 100644
index 0000000..0de933e
--- /dev/null
+++ b/core/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/f48a8de8/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java 
b/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
index d7f0d1a..9900459 100644
--- a/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
+++ b/core/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
@@ -720,6 +726,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/f48a8de8/core/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy
----------------------------------------------------------------------
diff --git 
a/core/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy 
b/core/src/test/groovy/org/apache/shiro/config/CommonsInterpolatorTest.groovy
new file mode 100644
index 0000000..851aeb0
--- /dev/null
+++ 
b/core/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/f48a8de8/core/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy
----------------------------------------------------------------------
diff --git 
a/core/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy 
b/core/src/test/groovy/org/apache/shiro/config/DefaultInterpolatorTest.groovy
new file mode 100644
index 0000000..4456068
--- /dev/null
+++ 
b/core/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/f48a8de8/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
----------------------------------------------------------------------
diff --git 
a/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy 
b/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
index 278efa2..44ec690 100644
--- a/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
+++ b/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
@@ -27,6 +27,7 @@ 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.
@@ -448,6 +449,63 @@ class ReflectionBuilderTest {
         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()

http://git-wip-us.apache.org/repos/asf/shiro/blob/f48a8de8/core/src/test/java/org/apache/shiro/config/SimpleBean.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/shiro/config/SimpleBean.java 
b/core/src/test/java/org/apache/shiro/config/SimpleBean.java
index 03a3094..dd557c9 100644
--- a/core/src/test/java/org/apache/shiro/config/SimpleBean.java
+++ b/core/src/test/java/org/apache/shiro/config/SimpleBean.java
@@ -31,6 +31,7 @@ public class SimpleBean {
     private int intProp;
     private byte[] byteArrayProp = null;
 
+    private List<String> stringList;
     private List<SimpleBean> simpleBeans;
 
     public SimpleBean() {
@@ -79,4 +80,12 @@ public class SimpleBean {
     public void setSimpleBeans(List<SimpleBean> simpleBeans) {
         this.simpleBeans = simpleBeans;
     }
+
+    public List<String> getStringList() {
+        return stringList;
+    }
+
+    public void setStringList(List<String> stringList) {
+        this.stringList = stringList;
+    }
 }

http://git-wip-us.apache.org/repos/asf/shiro/blob/f48a8de8/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1a3d9d6..69da491 100644
--- a/pom.xml
+++ b/pom.xml
@@ -670,6 +670,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>
                 <!-- Used for Atlassian Crowd Realm - not required for the 
framework: -->
                 <groupId>com.atlassian.crowd</groupId>

Reply via email to