You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ode.apache.org by mr...@apache.org on 2008/05/22 01:40:38 UTC

svn commit: r658934 [2/2] - in /ode/branches/APACHE_ODE_1.1: ./ bpel-api/src/main/java/org/apache/ode/bpel/common/ bpel-compiler/src/main/java/org/apache/ode/bpel/elang/xpath20/compiler/ bpel-runtime/src/main/java/org/apache/ode/bpel/elang/xpath20/runt...

Added: ode/branches/APACHE_ODE_1.1/utils/src/main/java/org/apache/ode/utils/URITemplate.java
URL: http://svn.apache.org/viewvc/ode/branches/APACHE_ODE_1.1/utils/src/main/java/org/apache/ode/utils/URITemplate.java?rev=658934&view=auto
==============================================================================
--- ode/branches/APACHE_ODE_1.1/utils/src/main/java/org/apache/ode/utils/URITemplate.java (added)
+++ ode/branches/APACHE_ODE_1.1/utils/src/main/java/org/apache/ode/utils/URITemplate.java Wed May 21 16:40:35 2008
@@ -0,0 +1,323 @@
+/*
+ * 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.ode.utils;
+
+import org.apache.commons.httpclient.URIException;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A partial implementation of URI Template expansion
+ * as specified by the <a href="http://bitworking.org/projects/URI-Templates/spec/draft-gregorio-uritemplate-03.html">URI template specification</a>.
+ * <p/><strong>Limitations</strong>
+ * <br/>The only operation implemented so far is <a href="http://bitworking.org/projects/URI-Templates/spec/draft-gregorio-uritemplate-03.html#var">Var substitution</a>. If an expansion template for another operation (join, neg, opt, etc) is found,
+ * an {@link UnsupportedOperationException} is thrown.
+ * <p/>
+ * <p/>
+ * <p/><strong>Escaping Considerations</strong>
+ * <br/>Replacement and default values are escaped. All characters except unreserved (as defined by <a href="http://tools.ietf.org/html/rfc2396#appendix-A">rfc2396</a>) are escaped.
+ * <br/> unreserved    = alphanum | mark
+ * <br/> mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |  "(" | ")"
+ * <p/>
+ * <a href="http://tools.ietf.org/html/rfc2396">Rfc2396</a> is used to be compliant with {@linkplain java.net.URI java.net.URI}.
+ * <p/>
+ * <p/><strong>Examples:</strong>
+ * <br/>
+ * Given the following template variable names and values:
+ * <ul>
+ * <li>foo = tag</li>
+ * <li>bar = java</li>
+ * <li>name = null</li>
+ * <li>date = 2008/05/09</li>
+ * </ul>
+ * <p/>The following URI Templates will be expanded as shown:
+ * <br/>http://example.com/{foo}/{bar}.{format=xml}
+ * <br/>http://example.com/tag/java.xml
+ * <br/>
+ * <br/>http://example.com/tag/java.{format}
+ * <br/>http://example.com/tag/java.
+ * <br/>
+ * <br/>http://example.com/{foo}/{name}
+ * <br/>http://example.com/tag/
+ * <br/>
+ * <br/>http://example.com/{foo}/{name=james}
+ * <br/>http://example.com/tag/james
+ * <br/>
+ * <br/>http://example.org/{date}
+ * <br/>http://example.org/2008%2F05%2F09
+ * <br/>
+ * <br/>http://example.org/{-join|&|foo,bar,xyzzy,baz}/{date}
+ * <br/>--> UnsupportedOperationException
+ *
+ * @author <a href="mailto:midon@intalio.com">Alexis Midon</a>
+ * @see #varSubstitution(String, Object[], java.util.Map)
+ */
+
+public class URITemplate {
+
+    private static final Log log = LogFactory.getLog(URITemplate.class);
+
+
+    public static final String EXPANSION_REGEX = "\\{[^\\}]+\\}";
+    // compiled pattern of the regex
+    private static final Pattern PATTERN = Pattern.compile(EXPANSION_REGEX);
+
+    /**
+     * Implements the function describes in <a href="http://bitworking.org/projects/URI-Templates/spec/draft-gregorio-uritemplate-03.html#appendix_a">the spec</a>
+     *
+     * @param expansion, an expansion template (with the surrounding braces)
+     * @return an array of object containing the operation name, the operation argument, a map of <var, default value (null if none)>
+     */
+    public static Object[] parseExpansion(String expansion) {
+        // remove surrounding braces if any
+        if (expansion.matches(EXPANSION_REGEX)) {
+            expansion = expansion.substring(1, expansion.length() - 1);
+        }
+        String[] r;
+        if (expansion.contains("|")) {
+            // (op, arg, vars)
+            r = expansion.split("\\|", -1);
+            // remove the leading '-' of the operation
+            r[0] = r[0].substring(1);
+        } else {
+            r = new String[]{null, null, expansion};
+        }
+
+        // parse the vars
+        Map vars = new HashMap();
+        String[] var = r[2].split(",");
+        for (String s : var) {
+            if (s.contains("=")) {
+                String[] a = s.split("=");
+                vars.put(a[0], a[1]);
+            } else {
+                vars.put(s, null);
+            }
+        }
+        // op, arg, vars
+        return new Object[]{r[0], r[1], vars};
+    }
+
+    /**
+     * Simply build a map from nameValuePairs and pass it to {@link #expand(String, java.util.Map)}
+     *
+     * @param nameValuePairs an array containing of name, value, name, value, and so on.  Null values are allowed.
+     * @see # expand (String, java.util.Map)
+     */
+    public static String expand(String uriTemplate, String... nameValuePairs) throws URIException, UnsupportedOperationException {
+        return expand(uriTemplate, toMap(nameValuePairs));
+    }
+
+    /**
+     * A partial implementation of URI Template expansion
+     * as specified by the <a href="http://bitworking.org/projects/URI-Templates/spec/draft-gregorio-uritemplate-03.html">URI template specification</a>.
+     * <p/>
+     * The only operation implemented as of today is "Var Substitution". If an expansion template for another operation (join, neg, opt, etc) is found,
+     * an {@link UnsupportedOperationException} will be thrown.
+     * <p/>
+     * See {@link #varSubstitution(String, Object[], java.util.Map)}
+     *
+     * @param uriTemplate    the URI template
+     * @param nameValuePairs a Map of &lt;name, value&gt;. Null values are allowed.
+     * @return a copy of uri template in which substitutions have been made (if possible)
+     * @throws URIException                  if the default protocol charset is not supported
+     * @throws UnsupportedOperationException if the operation is not supported. Currently only var substitution is supported.
+     * @see #varSubstitution(String, Object[], java.util.Map)
+     */
+    public static String expand(String uriTemplate, Map<String, String> nameValuePairs) throws URIException, UnsupportedOperationException {
+        return expand(uriTemplate, nameValuePairs, false);
+    }
+
+    /**
+     * Same as {@link #expand(String, java.util.Map)} but preserve an expansion template if the corresponding variable
+     * is not defined in the {@code nameValuePairs} map (i.e. map.contains(var)==false).
+     * <br/>Meaning that a template may be returned.
+     * <br/> If a default value exists for the undefined value, it will be used to replace the expansion pattern.
+     * <p/>
+     * <strong>Beware that this behavior deviates from the URI Template specification.</strong>
+     * <p/>
+     * For instance:
+     * <br/>Given the following template variable names and values:
+     * <ul>
+     * <li>bar = java</li>
+     * <li>foo undefined
+     * </ul>
+     * <p/>The following expansion templates will be expanded as shown if {@code preserveUndefinedVar} is true:
+     * <br/>http://example.com/{bar}
+     * <br/>http://example.com/java
+     * <br/>
+     * <br/>{foo=a_default_value}
+     * <br/>a_default_value
+     * <br/>
+     * <br/>http://example.com/{bar}/{foo}
+     * <br/>http://example.com/java/{foo}
+     *
+     * @see #expand(String, java.util.Map)
+     */
+    public static String expandLazily(String uriTemplate, Map<String, String> nameValuePairs) throws URIException, UnsupportedOperationException {
+        return expand(uriTemplate, nameValuePairs, true);
+    }
+
+    /**
+     * @see #expandLazily(String, java.util.Map)
+     */
+    public static String expandLazily(String uriTemplate, String... nameValuePairs) throws URIException {
+        return expandLazily(uriTemplate, toMap(nameValuePairs));
+    }
+
+
+    /**
+     * @see #varSubstitution(String, Object[], java.util.Map, boolean)
+     * @see #expandLazily(String, String[])
+     */
+    private static String expand(String uriTemplate, Map<String, String> nameValuePairs, boolean preserveUndefinedVar) throws URIException, UnsupportedOperationException {
+        Matcher m = PATTERN.matcher(uriTemplate);
+        // Strings are immutable in java
+        // so let's use a buffer, and append all substrings between 2 matches and the replacement value for each match 
+        StringBuilder sb = new StringBuilder(uriTemplate.length());
+        int prevEnd = 0;
+        while (m.find()) {
+            // append the string between two matches
+            sb.append(uriTemplate.substring(prevEnd, m.start()));
+            prevEnd = m.end();
+
+            // expansion pattern with braces
+            String expansionPattern = uriTemplate.substring(m.start(), m.end());
+            Object[] expansionInfo = parseExpansion(expansionPattern);
+            String operationName = (String) expansionInfo[0];
+            // here we have to know which operation apply
+            if (operationName != null) {
+                final String msg = "Operation not supported [" + operationName + "]. This expansion pattern [" + expansionPattern + "] is not valid.";
+                if (log.isWarnEnabled()) log.warn(msg);
+                throw new UnsupportedOperationException(msg);
+            } else {
+                // here we care only for var substitution, i.e expansion patterns with no operation name
+                sb.append(varSubstitution(expansionPattern, expansionInfo, nameValuePairs, preserveUndefinedVar));
+            }
+
+        }
+        if (sb.length() == 0) {
+            // return the template itself if no match (String are immutable in java, no need to clone the template)
+            return uriTemplate;
+        } else {
+            // don't forget the remaining part
+            sb.append(uriTemplate.substring(prevEnd, uriTemplate.length()));
+            return sb.toString();
+        }
+    }
+
+    /**
+     * An implementation of var substitution as defined by the
+     * <a href="http://bitworking.org/projects/URI-Templates/spec/draft-gregorio-uritemplate-03.html#var">URI template specification</a>.
+     * <p/>
+     * If for a given variable, the variable is in the name/value map but the associated value is null. The variable will be replaced with an empty string or with the default value if any.
+     *
+     * @param expansionPattern an expansion pattern (not a uri template) e.g. "{foo}"
+     * @param expansionInfo    the result of {@link #parseExpansion(String)} for the given expansion pattern
+     * @param nameValuePairs   the Map<String, String> of names and associated values. May containt null values.
+     * @return the expanded string, properly escaped.
+     * @throws URIException if an encoding exception occured
+     * @see org.apache.commons.httpclient.util.URIUtil#encodeWithinQuery(String)
+     * @see java.net.URI
+     */
+    public static String varSubstitution(String expansionPattern, Object[] expansionInfo, Map<String, String> nameValuePairs) throws URIException {
+        return varSubstitution(expansionPattern, expansionInfo, nameValuePairs, false);
+    }
+
+    /**
+     * Same as {@link #varSubstitution(String, Object[], java.util.Map)} but the {@code preserveUndefinedVar} boolean
+     * argument (if {@code true}) allows to preserve an expansion template if the corresponding variable is not defined in the {@code nameValuePairs} map (i.e. map.contains(var)==false).
+     * <br/> If a default value exists for the undefined value, it will be used to replace the expansion pattern.
+     * <p/>
+     * <strong>Beware that this behavior deviates from the URI Template specification.</strong>
+     * <p/>
+     * For instance:
+     * <br/>Given the following template variable names and values:
+     * <ul>
+     * <li>bar = java</li>
+     * <li>foo undefined
+     * </ul>
+     * <p/>The following expansion templates will be expanded as shown if {@code preserveUndefinedVar} is true:
+     * <br/>{bar}
+     * <br/>java
+     * <br/>
+     * <br/>{foo=a_default_value}
+     * <br/>a_default_value
+     * <br/>
+     * <br/>{foo}
+     * <br/>{foo}
+     */
+    public static String varSubstitution(String expansionPattern, Object[] expansionInfo, Map<String, String> nameValuePairs, boolean preserveUndefinedVar) throws URIException {
+        Map vars = (Map) expansionInfo[2];
+        // only one var per pattern
+        Map.Entry e = (Map.Entry) vars.entrySet().iterator().next();
+        String var = (String) e.getKey();
+        String defaultValue = (String) e.getValue();
+        boolean hasDefaultValue = defaultValue != null;
+        // this boolean indicates if the var is mentionned in the map, not that the associated value is not null.
+        boolean varDefined = nameValuePairs.containsKey(var);
+        String providedValue = nameValuePairs.get(var);
+        String res;
+        boolean escapingNeeded = true;
+        if (varDefined) {
+            if (providedValue == null && !hasDefaultValue) {
+                res = "";
+            } else {
+                res = providedValue != null ? providedValue : defaultValue;
+            }
+        } else {
+            // If the variable is undefined and no default value is given then substitute with the empty string,
+            // except if preserveUndefinedVar is true
+
+            if (hasDefaultValue) {
+                res = defaultValue;
+            } else {
+                if (preserveUndefinedVar) {
+                    res = expansionPattern;
+                    escapingNeeded = false;
+                } else {
+                    res = "";
+                }
+            }
+        }
+        // We assume that the replacement value is for the query part of the URI.
+        // Actually the query allows less character than the path part. $%&+,:@
+        // (acording to RFC2396
+        return escapingNeeded ? URIUtil.encodeWithinQuery(res) : res;
+    }
+
+
+    private static Map<String, String> toMap(String... nameValuePairs) {
+        if (nameValuePairs.length % 2 != 0) {
+            throw new IllegalArgumentException("An even number of elements is expected.");
+        }
+        Map<String, String> m = new HashMap<String, String>();
+        for (int i = 0; i < nameValuePairs.length; i = i + 2) {
+            m.put(nameValuePairs[i], nameValuePairs[i + 1]);
+        }
+        return m;
+    }
+}

Added: ode/branches/APACHE_ODE_1.1/utils/src/test/java/org/apache/ode/utils/URITemplateTest.java
URL: http://svn.apache.org/viewvc/ode/branches/APACHE_ODE_1.1/utils/src/test/java/org/apache/ode/utils/URITemplateTest.java?rev=658934&view=auto
==============================================================================
--- ode/branches/APACHE_ODE_1.1/utils/src/test/java/org/apache/ode/utils/URITemplateTest.java (added)
+++ ode/branches/APACHE_ODE_1.1/utils/src/test/java/org/apache/ode/utils/URITemplateTest.java Wed May 21 16:40:35 2008
@@ -0,0 +1,141 @@
+/*
+ * 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.ode.utils;
+
+import junit.framework.TestCase;
+
+import java.util.*;
+
+import org.apache.commons.httpclient.URIException;
+
+/**
+ * @author <a href="mailto:midon@intalio.com">Alexis Midon</a>
+ */
+public class URITemplateTest extends TestCase {
+    private static final String EXCEPTION_EXPECTED = "ExceptionExpected";
+
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testParseExpansion(){
+        Object[] expansionPatterns = new Object[]{
+                "{my_var}",  new Object[]{null, null, new HashMap(){{put("my_var",null);}}},
+                "{my_var=my_default}",  new Object[]{null, null, new HashMap(){{put("my_var","my_default");}}},
+                "{-suffix|/|foo}",  new Object[]{"suffix", "/", new HashMap(){{put("foo",null);}}},
+                "{-opt|fred@example.org|foo}",  new Object[]{"opt", "fred@example.org", new HashMap(){{put("foo",null);}}}
+        };
+
+        for (int i = 0; i < expansionPatterns.length; i=i+2) {
+            String patternInfo = (String) expansionPatterns[i];
+            Object[] computedResult = URITemplate.parseExpansion(patternInfo);
+            Object[] expectedResult = (Object[]) expansionPatterns[i + 1];
+            assertEquals("Unexpected operation", expectedResult[0], computedResult[0]);
+            assertEquals("Unexpected argument", expectedResult[1], computedResult[1]);
+            Map expectedVarMap = (Map) expectedResult[2];
+            Map computedVarMap = (Map) computedResult[2];
+            assertEquals("Var map do not have the number of elements", expectedVarMap.size(), computedVarMap.size());
+            for (Iterator it = expectedVarMap.entrySet().iterator(); it.hasNext();) {
+                Map.Entry e = (Map.Entry) it.next();
+                assertEquals("Different Value!", e.getValue(), computedVarMap.get(e.getKey()));
+            }
+        }
+    }
+
+
+    public void testExpand() throws Exception {
+        // template, input name/value array, expected result
+        Object[] templates = new Object[]{
+                "{a}", new String[]{"a", "hello"}, "hello"
+                ,"{a}", new String[]{"var_not_in_template", "hello"}, "{a}"
+                ,"{a=3}", new String[]{"var_not_in_template", "hello"}, "3" // with a default value
+                ,"hello {name}!", new String[]{"name", null}, "hello !"  // null value
+                ,"hello {name=darling}!", new String[]{"name", null}, "hello darling!"  // null value and a default
+                ,"hello {name=darling}!", new String[]{"name", "brother"}, "hello brother!"
+                ,"hello {name=darling}! what's {this}?", new String[]{"name", "brother", "this", "this"}, "hello brother! what's this?"
+                ,"hello {name=darling}! what's {this}?", new String[]{"name", "brother", "this", "wrong"}, "hello brother! what's wrong?"
+                ,"hello {name=darling}! what's {this}?", new String[]{"name", "brother", "this", null}, "hello brother! what's ?"
+                ,"hello {name=darling}! what's {this=up}?", new String[]{"name", "brother", "this", null}, "hello brother! what's up?"
+                ,"hello {name=darling}! what's {this=up}?", new String[]{"name", "brother"}, "hello brother! what's up?"
+                ,"hello {name}! what's {this}?", new String[]{"var_not_in_template", "foo"}, "hello ! what's ?"
+                ,"hello{name}what's{this}", new String[]{"name", " brother! ", "this", " this?"}, "hello%20brother!%20what's%20this%3F" // test encoding
+                ,"hello{name= brother! }what's{this}", new String[]{"this", " this?"}, "hello%20brother!%20what's%20this%3F" // test encoding + default value
+                ,"hello {name=darling}! what's {this}?", new String[]{"name", "brother", "this", "{wrong}"}, "hello brother! what's %7Bwrong%7D?"
+                ,"hello%20brother!%20what's{this}", new String[]{"this", " this?"}, "hello%20brother!%20what's%20this%3F" // test template of template
+                ,"{this}", new String[]{"this", ";/?:@&=+,$"}, "%3B%2F%3F%3A%40%26%3D%2B%2C%24" // reserved characters within a query
+                ,"{this}", new String[]{"this", "somereserved%;/?:@&=+,$allunreserved-_.!~*'()"}, "somereserved%25%3B%2F%3F%3A%40%26%3D%2B%2C%24allunreserved-_.!~*'()" // reserved characters within a query
+                // the followings are included in the javadoc as examples
+                , "http://example.com/{foo}/{bar}.{format=xml}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"},"http://example.com/tag/java.xml" // undefined var with a default value
+                , "http://example.com/tag/java.{format}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"},"http://example.com/tag/java." // undefined and no default
+                , "http://example.com/{foo}/{name}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"},"http://example.com/tag/"
+                , "http://example.com/{foo}/{name=james}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"},"http://example.com/tag/james"
+                , "http://example.org/{date}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"}, "http://example.org/2008%2F05%2F09"
+                , "http://example.org/{-join|&|foo,bar,xyzzy,baz}/{date}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"}, EXCEPTION_EXPECTED
+        };
+        for (int i = 0; i < templates.length; i = i + 3) {
+            String template = (String) templates[i];
+            String[] pairs = (String[]) templates[i + 1];
+            String expected = (String) templates[i + 2];
+            String computed = null;
+            try {
+                computed = URITemplate.expand(template, pairs);
+                if (EXCEPTION_EXPECTED.equals(expected)) {
+                    fail("An exception was supposed to be thrown!");
+                } else {
+                    assertEquals("Test #" + ((i / 3) + 1) + ": Result does not match expectation.", expected, computed);
+                }
+            } catch (Exception e) {
+                if (!EXCEPTION_EXPECTED.equals(expected)) {
+                    // this exception was NOT expected!
+                    throw e;
+                }
+            }
+        }
+    }
+
+    public void testExpandLazily() throws Exception {
+        // same but with some undefined vars
+        // template, input name/value array, expected result
+        Object[] templates = new Object[]{
+                "http://example.com/{foo}/{bar}.{format}", new String[]{"foo", "tag", "bar", "java", "name", null, "date", "2008/05/09"},"http://example.com/tag/java.{format}" // undefined var with no default value
+                , "http://example.com/{foo}/{name}", new String[]{"bar", "java", "date", "2008/05/09"},"http://example.com/{foo}/{name}"
+                , "http://example.com/{foo}/{name=james}", new String[]{"bar", "java", "name", null, "date", "2008/05/09"},"http://example.com/{foo}/james"
+        };
+        for (int i = 0; i < templates.length; i = i + 3) {
+            String template = (String) templates[i];
+            String[] pairs = (String[]) templates[i + 1];
+            String expected = (String) templates[i + 2];
+            String computed = null;
+            try {
+                computed = URITemplate.expandLazily(template, pairs);
+                if (EXCEPTION_EXPECTED.equals(expected)) {
+                    fail("An exception was supposed to be thrown!");
+                } else {
+                    assertEquals("Test #" + ((i / 3) + 1) + ": Result does not match expectation.", expected, computed);
+                }
+            } catch (Exception e) {
+                if (!EXCEPTION_EXPECTED.equals(expected)) {
+                    // this exception was NOT expected!
+                    throw e;
+                }
+            }
+        }
+    }
+}