You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by Nathan Bubna <nb...@gmail.com> on 2017/01/02 14:13:19 UTC

Re: svn commit: r1776916 [1/2] - in /velocity/tools/trunk: ./ src/changes/ velocity-tools-assembly/ velocity-tools-assembly/src/main/assembly/ velocity-tools-examples/velocity-tools-examples-showcase/ velocity-tools-examples/velocity-tools-examples-showcas...

Wow. Sounds great, Claude!

On Mon, Jan 2, 2017 at 2:49 AM, <cb...@apache.org> wrote:

> Author: cbrisson
> Date: Mon Jan  2 10:49:55 2017
> New Revision: 1776916
>
> URL: http://svn.apache.org/viewvc?rev=1776916&view=rev
> Log:
> [tools] ImportTool and XmlTool reenginering, along with the addition of a
> JsonTool.
>
> The XmlTool now uses the standard JRE parser rather than the external jdom
> library. Thus, the velocity-tools-xml module has been removed, and XmlTool
> has been incorporated to the generic and view modules.
>
> ImportTool and ImportSupport have been splitted between the generic and
> the view packages, as detailed below. The terminology used by the
> ImportSupport has been reviewed (use local/remote URL instead of
> 'absolute'/'relative' URL).
>
> The generic.ImportSupport and view.ViewImportSupport classes give a basic
> API for:
>  - reading a local resource FIRST from a File (generic version) or from
> the webapp (view version), THEN, if not found from the classpath.
>  - fetching a local URL (only applicable for the view version), which
> allows to mix several J2EE view technologies in the same container.
>  - fetching a remote URL (which is forbidden in safe mode for the view
> version).
>
> generic.ImportTool and view.ImportTool expose read(String resource) and
> fetch(String url) methods, based on the ImportSupport and ViewImportSupport
> APIs.
>
> generic.XmlTool and view.XmlTool expose parse(String xml), read(String
> xmlResource) and fetch(String url) methods, based on the ImportSupport and
> ViewImportSupport APIs. Both accept the 'resource=<resource_path>' and
> 'source=<url>' configuration parameters.
>
> generic.JsonTool and view.JsonTool expose parse(String json), read(String
> jsonResource) and fetch(String url) methods, based on the ImportSupport and
> ViewImportSupport APIs. Both accept the 'resource=<resource_path>' and
> 'source=<url>' configuration parameters.
>
> Whenever no specific configuration source has been given, view.XmlTool and
> view.JsonTool add the ability to parse the posted request body, if it
> corresponds to an xml or json mime type.
>
> Some remarks about the implementation:
>  - I choosed to shade the org.json library, to avoid another dependency.
>  - The XmlUtils class provides a static pool of xml parsers along with
> several xml utilities
>  - feel free to make alternate proposals for the tools API and
> configuration ; in particular, for the the "resource=<local resource>" and
> "source=<url>" terminology.
>
> Happy new year, by the way.
>
>
> Added:
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/json.vm
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/post_json.vm
>     velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/XmlUtils.java
>     velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportSupport.java
>     velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportTool.java
>       - copied, changed from r1770862, velocity/tools/trunk/velocity-
> tools-view/src/main/java/org/apache/velocity/tools/view/ImportTool.java
>     velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/JsonTool.java
>     velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/XmlTool.java
>     velocity/tools/trunk/velocity-tools-generic/src/test/java/
> org/apache/velocity/tools/generic/JsonToolTests.java
>       - copied, changed from r1770862, velocity/tools/trunk/velocity-
> tools-generic/src/test/java/org/apache/velocity/tools/
> generic/ClassToolTests.java
>     velocity/tools/trunk/velocity-tools-generic/src/test/java/
> org/apache/velocity/tools/generic/XmlToolTests.java
>     velocity/tools/trunk/velocity-tools-generic/src/test/
> resources/file.xml
>     velocity/tools/trunk/velocity-tools-generic/src/test/
> resources/foo.json
>     velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/JsonTool.java
>     velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/ViewImportSupport.java
>       - copied, changed from r1776915, velocity/tools/trunk/velocity-
> tools-view/src/main/java/org/apache/velocity/tools/view/ImportSupport.java
>     velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/XmlTool.java
> Removed:
>     velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/ImportSupport.java
>     velocity/tools/trunk/velocity-tools-xml/
> Modified:
>     velocity/tools/trunk/pom.xml
>     velocity/tools/trunk/src/changes/changes.xml
>     velocity/tools/trunk/velocity-tools-assembly/pom.xml
>     velocity/tools/trunk/velocity-tools-assembly/src/main/assembly/all.xml
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/pom.xml
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/resources/resources.properties
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/collection.vm
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/toolmenu.vm
>     velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/test/java/org/apache/velocity/
> examples/showcase/ViewToolsIT.java
>     velocity/tools/trunk/velocity-tools-generic/pom.xml
>     velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/ConversionUtils.java
>     velocity/tools/trunk/velocity-tools-generic/src/main/
> resources/org/apache/velocity/tools/generic/tools.xml
>     velocity/tools/trunk/velocity-tools-generic/src/test/java/
> org/apache/velocity/tools/test/whitebox/GenericToolsTests.java
>     velocity/tools/trunk/velocity-tools-uberjar/pom.xml
>     velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/BrowserTool.java
>     velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/ImportTool.java
>     velocity/tools/trunk/velocity-tools-view/src/main/resources/
> org/apache/velocity/tools/view/tools.xml
>
> Modified: velocity/tools/trunk/pom.xml
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/pom.xml?
> rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/pom.xml (original)
> +++ velocity/tools/trunk/pom.xml Mon Jan  2 10:49:55 2017
> @@ -128,7 +128,6 @@
>      </scm>
>      <modules>
>          <module>velocity-tools-generic</module>
> -        <module>velocity-tools-xml</module>
>          <module>velocity-tools-view</module>
>          <module>velocity-tools-view-jsp</module>
>          <module>velocity-tools-uberjar</module>
>
> Modified: velocity/tools/trunk/src/changes/changes.xml
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/src/
> changes/changes.xml?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/src/changes/changes.xml (original)
> +++ velocity/tools/trunk/src/changes/changes.xml Mon Jan  2 10:49:55 2017
> @@ -26,6 +26,44 @@
>    <body>
>
>      <release version="3.0-SNAPSHOT" date="In Subversion">
> +      <action type="add" dev="cbrisson">
> +        ImportTool reenginering:
> +        <ul>
> +          <li>the ImportSupport utility class has been splitted between
> o.a.v.generic.ImportSupport and o.a.v.view.ViewImportSupport</li>
> +          <li>the ImportTool now has a generic version (for remote URLs
> import) and a view version (for local URLs import) which cannot use remote
> URLs in safe mode</li>
> +        </ul>
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        XmlTool now uses the standard JRE XML parser instead of the
> org.jdom API ; it is now in two flavors, the generic tools one and the view
> tools one. The view tools flavor
> +        allows the parsing of http query xml post data.
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        Added a new JsonTool for parsing json. It is in two flavors, the
> generic tools one and the view tools one. The view tools flavor
> +        allows the parsing of http query json post data.
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        Added an EscapeTool.unurl(String) unescaping method
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        Deprecated ConversionTool: date/time parsing and formatting
> methods belong to DateTool, while number parsing
> +        and formatting methods belong to NumberTool ; toLocale() method
> is now in LocaleConfig
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        Deprecated SortTool, and added a CollectionTool to gather lists
> sorting/splitting methods
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        Deprecated MathTool number parsing methods, which are redundant
> with NumberTool ones
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        Deprecated AlternateTool
> +      </action>
> +      <action type="add" dev="cbrisson">
> +        DateTool reenginering:
> +        <ul>
> +          <li>added iso and iso_tz date/datetime standard formats</li>
> +          <li>added intl and intl_tz for human-readable international
> format (time zone displayed by id)</li>
> +        </ul>
> +      </action>
>          <action type="fix" dev="cbrisson">
>              use static logging in Tools classes
>          </action>
>
> Modified: velocity/tools/trunk/velocity-tools-assembly/pom.xml
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-assembly/pom.xml?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-assembly/pom.xml (original)
> +++ velocity/tools/trunk/velocity-tools-assembly/pom.xml Mon Jan  2
> 10:49:55 2017
> @@ -73,13 +73,6 @@
>          </dependency>
>          <dependency>
>              <groupId>org.apache.velocity</groupId>
> -            <artifactId>velocity-tools-xml</artifactId>
> -            <version>${project.version}</version>
> -            <type>jar</type>
> -            <scope>compile</scope>
> -        </dependency>
> -        <dependency>
> -            <groupId>org.apache.velocity</groupId>
>              <artifactId>velocity-tools-examples-simple</artifactId>
>              <version>${project.version}</version>
>              <type>war</type>
>
> Modified: velocity/tools/trunk/velocity-tools-assembly/src/main/
> assembly/all.xml
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-assembly/src/main/assembly/all.xml?rev=1776916&
> r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-assembly/src/main/assembly/all.xml
> (original)
> +++ velocity/tools/trunk/velocity-tools-assembly/src/main/assembly/all.xml
> Mon Jan  2 10:49:55 2017
> @@ -83,14 +83,6 @@
>              </includes>
>          </fileSet>
>          <fileSet>
> -            <directory>../velocity-tools-xml</directory>
> -            <outputDirectory>src/velocity-tools-xml</outputDirectory>
> -            <includes>
> -                <include>pom.xml</include>
> -                <include>src/</include>
> -            </includes>
> -        </fileSet>
> -        <fileSet>
>              <directory>../velocity-tools-view</directory>
>              <outputDirectory>src/velocity-tools-view</outputDirectory>
>              <includes>
> @@ -155,10 +147,6 @@
>              <outputDirectory>docs/velocity-tools-generic</
> outputDirectory>
>          </fileSet>
>          <fileSet>
> -            <directory>../velocity-tools-xml/target/site</directory>
> -            <outputDirectory>docs/velocity-tools-xml</outputDirectory>
> -        </fileSet>
> -        <fileSet>
>              <directory>../velocity-tools-view/target/site</directory>
>              <outputDirectory>docs/velocity-tools-view</outputDirectory>
>          </fileSet>
>
> Modified: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/pom.xml
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/pom.xml?rev=
> 1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-examples-showcase/pom.xml
> (original)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-examples-showcase/pom.xml
> Mon Jan  2 10:49:55 2017
> @@ -63,7 +63,7 @@
>                          <properties>
>                              <cargo.jvmargs>
>                                  -Xdebug
> -                                -Xrunjdwp:transport=dt_socket,
> server=y,suspend=n,address=5005
> +                                -Xrunjdwp:transport=dt_socket,
> server=n,suspend=y,address=5005
>                                  -Xnoagent
>                                  -Djava.compiler=NONE
>                              </cargo.jvmargs>
> @@ -126,12 +126,6 @@
>          </dependency>
>          <dependency>
>                 <groupId>org.apache.velocity</groupId>
> -               <artifactId>velocity-tools-xml</artifactId>
> -               <version>${project.version}</version>
> -               <scope>runtime</scope>
> -        </dependency>
> -        <dependency>
> -               <groupId>org.apache.velocity</groupId>
>                 <artifactId>velocity-tools-view-jsp</artifactId>
>                 <version>${project.version}</version>
>                 <type>jar</type>
>
> Modified: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/resources/resources.properties
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/src/main/
> resources/resources.properties?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/resources/resources.properties (original)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/resources/resources.properties Mon Jan  2
> 10:49:55 2017
> @@ -76,6 +76,7 @@ tools.esc = EscapeTool
>  tools.field = FieldTool
>  tools.include = IncludeTool
>  tools.import = ImportTool
> +tools.json = JsonTool
>  tools.jsp = JSP taglib
>  tools.link = LinkTool
>  tools.lists = ListTool
> @@ -164,6 +165,7 @@ class.isStrict = Returns <code>true</cod
>  class.supportsNewInstance = Returns <code>true</code> if a new instance
> of the class being inspected can be created via $class.type.newInstance().
>  class.toString = Returns the result of $class.type.toString().
>
> +collection.intro = Collection tool which provides String splitting
> methods and various collections sorting methods.
>  collection.getStringsDelimiter = Returns the delimiter to be used when
> splitting a string.
>  collection.getStringsTrim = Returns whether or not to trim string
> elements found when splitting a string.
>  collection.sort_Collection = Sort a collection.
> @@ -340,6 +342,9 @@ the name parameter is returned.
>  include.find_StringString.param1 = ''demo.vm''
>  include.find_StringString.param2 = ''fr''
>
> +# json.vm resources
> +json.intro = Json parsing tool
> +
>  # loop.vm resources
>  loop.intro = This tool is a convenience tool to use with #foreach loops.
> It wraps a \
>  list to let you prematurely stop iterating, skip iterations, sync
> iteration over \
>
> Modified: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/collection.vm
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/src/main/
> webapp/collection.vm?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/collection.vm (original)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/collection.vm Mon Jan  2 10:49:55 2017
> @@ -14,18 +14,17 @@
>  ## KIND, either express or implied.  See the License for the
>  ## specific language governing permissions and limitations
>  ## under the License.
> -#title( 'ConversionTool' )
> +#title( 'CollectionTool' )
>  <p>
>  $text.demo.thisPage.insert("#doclink( 'CollectionTool' true )").
> +</p>
> +<p>
>  $text.collection.intro
>  </p>
>
> -## The demo.vm template expects the following values to be set
> -#set( $toollink = $doclink )
> -#set( $toolname = 'collection' )
> -#set( $toolclass = $collection.class )
> -#set( $toolDemo =
> -""
> -)
> +#demoTableStart()
> +
> +#demo1( 'collection' 'split' 5 'split string' )
> +#demoCustom( "sorter.sort(['b','c','a'])" )
>
> -#parse( 'demo.vm' )
> +</table>
>
> Added: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/json.vm
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/src/main/
> webapp/json.vm?rev=1776916&view=auto
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/json.vm (added)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/json.vm Mon Jan  2 10:49:55 2017
> @@ -0,0 +1,29 @@
> +## 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.
> +#title( 'JsonTool' )
> +<p>
> +$text.demo.thisPage.insert("#doclink( 'JsonTool' true )").
> +</p>
> +<p>
> +$text.json.intro
> +</p>
> +
> +#demoTableStart()
> +
> +#demo1( 'json' 'parse' 5 'parse JSON string' )
> +
> +</table>
>
> Added: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/post_json.vm
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/src/main/
> webapp/post_json.vm?rev=1776916&view=auto
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/post_json.vm (added)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/post_json.vm Mon Jan  2 10:49:55 2017
> @@ -0,0 +1,18 @@
> +## 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.
> +#title( 'JsonTool test template' )
> +<span id="foo">$json.foo</span>
>
> Modified: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/toolmenu.vm
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/src/main/
> webapp/toolmenu.vm?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/toolmenu.vm (original)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/main/webapp/toolmenu.vm Mon Jan  2 10:49:55 2017
> @@ -35,6 +35,7 @@
>  #toolMenuItem( $llink 'field' )
>  #toolMenuItem( $llink 'import' )
>  #toolMenuItem( $llink 'include' )
> +#toolMenuItem( $llink 'json' )
>  #toolMenuItem( $llink 'jsp' )
>  #toolMenuItem( $llink 'link' )
>  #toolMenuItem( $llink 'loop' )
>
> Modified: velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/test/java/org/apache/velocity/
> examples/showcase/ViewToolsIT.java
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-examples/velocity-tools-examples-showcase/src/test/
> java/org/apache/velocity/examples/showcase/ViewToolsIT.
> java?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/test/java/org/apache/velocity/
> examples/showcase/ViewToolsIT.java (original)
> +++ velocity/tools/trunk/velocity-tools-examples/velocity-tools-
> examples-showcase/src/test/java/org/apache/velocity/
> examples/showcase/ViewToolsIT.java Mon Jan  2 10:49:55 2017
> @@ -24,11 +24,16 @@ import static org.junit.Assert.assertNot
>  import static org.junit.Assert.assertTrue;
>  import static org.junit.Assert.fail;
>
> +import java.io.ByteArrayInputStream;
>  import java.io.IOException;
> +import java.io.InputStream;
> +import java.io.InputStreamReader;
>  import java.io.PrintWriter;
> +import java.nio.charset.Charset;
>  import java.util.regex.Matcher;
>  import java.util.regex.Pattern;
>
> +import com.meterware.httpunit.PostMethodWebRequest;
>  import org.junit.BeforeClass;
>  import org.junit.Test;
>
> @@ -143,7 +148,7 @@ public class ViewToolsIT {
>       */
>      private WebResponse submitWithParam(WebResponse orig, String
> formname, String paramname, String value) throws Exception {
>          WebForm form = orig.getFormWithName(formname);
> -        form.setParameter(paramname,value);
> +        form.setParameter(paramname, value);
>          return form.submit();
>      }
>
> @@ -187,24 +192,24 @@ public class ViewToolsIT {
>          WebResponse resp = conv.getResponse(req);
>
>          /* check that getThis() is a ViewToolContext instance */
> -        checkTextStart(resp,"getThis()","org.apache.velocity.tools.
> view.ViewToolContext");
> +        checkTextStart(resp, "getThis()", "org.apache.velocity.tools.
> view.ViewToolContext");
>
>          /* check contains('context') */
>          resp = submitWithParam(resp,"contains_Object","contains_
> Object1","'context'");
> -        checkText(resp,"contains(java.lang.Object)","true");
> +        checkText(resp, "contains(java.lang.Object)", "true");
>
>          /* check get('context') */
> -        resp = submitWithParam(resp,"get_Object","get_Object1","'
> context'");
> -        checkTextStart(resp,"get(java.lang.Object)","org.apache.
> velocity.tools.view.ViewContextTool");
> +        resp = submitWithParam(resp, "get_Object", "get_Object1",
> "'context'");
> +        checkTextStart(resp, "get(java.lang.Object)",
> "org.apache.velocity.tools.view.ViewContextTool");
>
>          /* check keys (the only expected uppercase is in 'velocityCount')
> */
> -        checkTextRegex(resp,"getKeys()","^\\[[a-z_A-Z]+(?:,\\s*[a-z_
> A-Z]+)*\\]$");
> +        checkTextRegex(resp, "getKeys()", "^\\[[a-z_A-Z]+(?:,\\s*[a-z_A-
> Z]+)*\\]$");
>
>          /* check toolbox */
>          checkTextRegex(resp,"getToolbox()","^\\{[a-z_A-Z]+=
> .*(?:,\\s*[a-z_A-Z]+=.*)*\\}$");
>
>          /* check values */
> -        checkTextStartEnd(resp,"getValues()","[","]");
> +        checkTextStartEnd(resp, "getValues()", "[", "]");
>      }
>
>      public @Test void testLinkTool() throws Exception {
> @@ -316,4 +321,14 @@ public class ViewToolsIT {
>          /* check all */
>          checkTextRegex(resp,"all","^\\{.*\\}$");
>      }
> +
> +    public @Test void testJsonTool() throws Exception
> +    {
> +        String json = "{\"foo\":\"bar\"}";
> +        WebConversation conv = new WebConversation();
> +        WebRequest req = new PostMethodWebRequest(ROOT_URL+"post_json.vm",
> new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))),
> "application/json");
> +        WebResponse resp = conv.getResponse(req);
> +        checkText(resp, "foo", "bar");
> +    }
> +
>  }
>
> Modified: velocity/tools/trunk/velocity-tools-generic/pom.xml
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-generic/pom.xml?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-generic/pom.xml (original)
> +++ velocity/tools/trunk/velocity-tools-generic/pom.xml Mon Jan  2
> 10:49:55 2017
> @@ -30,6 +30,38 @@
>    <artifactId>velocity-tools-generic</artifactId>
>    <name>Apache Velocity Tools - Generic tools</name>
>    <description>Generic tools that can be used in any
> context.</description>
> +  <build>
> +      <plugins>
> +          <plugin>
> +              <groupId>org.apache.maven.plugins</groupId>
> +              <artifactId>maven-shade-plugin</artifactId>
> +              <version>2.4.3</version>
> +              <executions>
> +                  <execution>
> +                      <id>shade</id>
> +                      <phase>package</phase>
> +                      <goals>
> +                          <goal>shade</goal>
> +                      </goals>
> +                      <configuration>
> +                          <artifactSet>
> +                              <includes>
> +                                  <include>org.json</include>
> +                              </includes>
> +                          </artifactSet>
> +                          <relocations>
> +                              <relocation>
> +                                  <pattern>org.json</pattern>
> +                                  <shadedPattern>org.apache.
> velocity.tools.shaded.org.json</shadedPattern>
> +                              </relocation>
> +                          </relocations>
> +                          <minimizeJar>true</minimizeJar>
> +                      </configuration>
> +                  </execution>
> +              </executions>
> +          </plugin>
> +      </plugins>
> +  </build>
>    <dependencies>
>        <dependency>
>            <groupId>org.apache.velocity</groupId>
> @@ -63,5 +95,10 @@
>            <version>${slf4j.version}</version>
>            <scope>test</scope>
>        </dependency>
> +      <dependency>
> +          <groupId>org.json</groupId>
> +          <artifactId>json</artifactId>
> +          <version>20160810</version>
> +      </dependency>
>    </dependencies>
>  </project>
>
> Modified: velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/ConversionUtils.java
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-generic/src/main/java/org/apache/velocity/tools/
> ConversionUtils.java?rev=1776916&r1=1776915&r2=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/ConversionUtils.java (original)
> +++ velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/ConversionUtils.java Mon Jan  2 10:49:55 2017
> @@ -813,7 +813,7 @@ public class ConversionUtils
>       * @see File
>       * @see ClassUtils#getResource(String,Object)
>       * @see URL
> -     */
> +    */
>      public static URL toURL(String value, Object caller)
>      {
>          try
>
> Added: velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/XmlUtils.java
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-generic/src/main/java/org/apache/velocity/tools/
> XmlUtils.java?rev=1776916&view=auto
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/XmlUtils.java (added)
> +++ velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/XmlUtils.java Mon Jan  2 10:49:55 2017
> @@ -0,0 +1,529 @@
> +/*
> + * 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.velocity.tools;
> +
> +import org.apache.velocity.runtime.RuntimeConstants;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +import org.w3c.dom.Attr;
> +import org.w3c.dom.Element;
> +import org.w3c.dom.NamedNodeMap;
> +import org.w3c.dom.Node;
> +import org.w3c.dom.NodeList;
> +import org.xml.sax.ErrorHandler;
> +import org.xml.sax.InputSource;
> +import org.xml.sax.SAXException;
> +import org.xml.sax.SAXParseException;
> +
> +import java.io.Reader;
> +import java.io.StringReader;
> +import java.io.StringWriter;
> +import java.lang.ref.SoftReference;
> +import java.util.Stack;
> +import java.util.concurrent.LinkedBlockingDeque;
> +
> +import javax.xml.XMLConstants;
> +import javax.xml.parsers.DocumentBuilder;
> +import javax.xml.parsers.DocumentBuilderFactory;
> +import javax.xml.parsers.ParserConfigurationException;
> +import javax.xml.transform.OutputKeys;
> +import javax.xml.transform.Transformer;
> +import javax.xml.transform.TransformerException;
> +import javax.xml.transform.TransformerFactory;
> +import javax.xml.transform.dom.DOMSource;
> +import javax.xml.transform.stream.StreamResult;
> +import javax.xml.xpath.XPath;
> +import javax.xml.xpath.XPathConstants;
> +import javax.xml.xpath.XPathExpression;
> +import javax.xml.xpath.XPathExpressionException;
> +import javax.xml.xpath.XPathFactory;
> +
> +/**
> + * <p>Utility class for simplifying parsing of xml documents. Documents
> are not validated, and
> + * loading of external files (xinclude, external entities, DTDs, etc.)
> are disabled.</p>
> + *
> + * @author Claude Brisson
> + * @since 3.0
> + * @version $$
> + */
> +public final class XmlUtils
> +{
> +    /* several pieces of code were borrowed from the Apache Shindig
> XmlUtil class.*/
> +
> +    private static final Logger LOGGER = LoggerFactory.getLogger(
> XmlUtils.class);
> +
> +    /** Handles xml errors so that they're not logged to stderr.
> +     */
> +    private static final ErrorHandler errorHandler = new ErrorHandler()
> +    {
> +        public void error(SAXParseException exception) throws SAXException
> +        {
> +            throw exception;
> +        }
> +        public void fatalError(SAXParseException exception) throws
> SAXException
> +        {
> +            throw exception;
> +        }
> +        public void warning(SAXParseException exception)
> +        {
> +            LOGGER.info("warning during parsing", exception);
> +        }
> +    };
> +
> +    private static boolean canReuseBuilders = false;
> +
> +    private static final DocumentBuilderFactory builderFactory =
> DocumentBuilderFactory.newInstance();
> +
> +    private static final ThreadLocal<DocumentBuilder> reusableBuilder
> +        = new ThreadLocal<DocumentBuilder>() {
> +        @Override
> +        protected DocumentBuilder initialValue() {
> +            try
> +            {
> +                LOGGER.trace("Created a new document builder");
> +                return builderFactory.newDocumentBuilder();
> +            }
> +            catch (ParserConfigurationException e)
> +            {
> +                throw new RuntimeException(e);
> +            }
> +        }
> +    };
> +
> +    static
> +    {
> +        // Namespace support is required for <os:> elements
> +        builderFactory.setNamespaceAware(true);
> +
> +        // Disable various insecure and/or expensive options.
> +        builderFactory.setValidating(false);
> +
> +        // Can't disable doctypes entirely because they're usually
> harmless. External entity
> +        // resolution, however, is both expensive and insecure.
> +        try
> +        {
> +            builderFactory.setAttribute("http://xml.org/sax/features/
> external-general-entities", false);
> +        }
> +        catch (IllegalArgumentException e)
> +        {
> +            // Not supported by some very old parsers.
> +            LOGGER.info("Error parsing external general entities: ", e);
> +        }
> +
> +        try
> +        {
> +            builderFactory.setAttribute("http://xml.org/sax/features/
> external-parameter-entities", false);
> +        }
> +        catch (IllegalArgumentException e)
> +        {
> +            // Not supported by some very old parsers.
> +            LOGGER.info("Error parsing external parameter entities: ", e);
> +        }
> +
> +        try
> +        {
> +            builderFactory.setAttribute("http://apache.org/xml/features/
> nonvalidating/load-external-dtd", false);
> +        }
> +        catch (IllegalArgumentException e)
> +        {
> +            // Only supported by Apache's XML parsers.
> +            LOGGER.info("Error parsing external DTD: ", e);
> +        }
> +
> +        try
> +        {
> +            builderFactory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING,
> true);
> +        }
> +        catch (IllegalArgumentException e)
> +        {
> +            // Not supported by older parsers.
> +            LOGGER.info("Error parsing secure XML: ", e);
> +        }
> +
> +        try
> +        {
> +            DocumentBuilder builder = builderFactory.
> newDocumentBuilder();
> +            builder.reset();
> +            canReuseBuilders = true;
> +            LOGGER.trace("reusing document builders");
> +        }
> +        catch (UnsupportedOperationException e)
> +        {
> +            // Only supported by newer parsers (xerces 2.8.x+ for
> instance).
> +            canReuseBuilders = false;
> +            LOGGER.trace("not reusing document builders");
> +        }
> +        catch (ParserConfigurationException e)
> +        {
> +            // Only supported by newer parsers (xerces 2.8.x+ for
> instance).
> +            canReuseBuilders = false;
> +            LOGGER.trace("not reusing document builders");
> +        }
> +    }
> +
> +    private static LinkedBlockingDeque<SoftReference<DocumentBuilder>>
> builderPool = new LinkedBlockingDeque<SoftReference<DocumentBuilder>>();
> // contains only idle builders
> +    private static int maxBuildersCount = 100;
> +    private static int currentBuildersCount = 0;
> +    private static final String BUILDER_MAX_INSTANCES_KEY =
> "velocity.tools.xml.documentbuilder.max.instances";
> +
> +    static
> +    {
> +        /* We're in a static code portion, so use a system property so
> that the DocumentBuilder pool size
> +        * remains configurable. */
> +        try
> +        {
> +            String configuredMax = System.getProperty(BUILDER_
> MAX_INSTANCES_KEY);
> +            if (configuredMax != null)
> +            {
> +                maxBuildersCount = Integer.parseInt(configuredMax);
> +            }
> +        }
> +        catch(Exception e)
> +        {
> +            LOGGER.error("could not configure XML document builder max
> instances count", e);
> +        }
> +    }
> +
> +    private static synchronized DocumentBuilder getDocumentBuilder()
> +    {
> +        DocumentBuilder builder = null;
> +        if (canReuseBuilders && builderPool.size() > 0)
> +        {
> +            builder = builderPool.pollFirst().get();
> +        }
> +        if (builder == null)
> +        {
> +            if(!canReuseBuilders || currentBuildersCount <
> maxBuildersCount)
> +            {
> +                try
> +                {
> +                    builder = builderFactory.newDocumentBuilder();
> +                    builder.setErrorHandler(errorHandler);
> +                    ++currentBuildersCount;
> +                }
> +                catch(Exception e)
> +                {
> +                    /* this is a fatal error */
> +                    throw new RuntimeException("could not create a new
> XML DocumentBuilder instance", e);
> +                }
> +            }
> +            else
> +            {
> +                try
> +                {
> +                    LOGGER.warn("reached XML DocumentBuilder pool size
> limit, current thread needs to wait; you can increase pool size with the {}
> system property", BUILDER_MAX_INSTANCES_KEY);
> +                    builder = builderPool.takeFirst().get();
> +                }
> +                catch(InterruptedException ie)
> +                {
> +                    LOGGER.warn("caught an InterruptedException while
> waiting for a DocumentBuilder instance");
> +                }
> +            }
> +        }
> +        return builder;
> +    }
> +
> +    private static synchronized void releaseBuilder(DocumentBuilder
> builder)
> +    {
> +        builder.reset();
> +        builderPool.addLast(new SoftReference<DocumentBuilder>(builder));
> +    }
> +
> +    private XmlUtils() {}
> +
> +    /**
> +     * Extracts an attribute from a node.
> +     *
> +     * @param node
> +     * @param attr
> +     * @param def
> +     * @return The value of the attribute, or def
> +     */
> +    public static String getAttribute(Node node, String attr, String def)
> +    {
> +        NamedNodeMap attrs = node.getAttributes();
> +        Node val = attrs.getNamedItem(attr);
> +        if (val != null)
> +        {
> +            return val.getNodeValue();
> +        }
> +        return def;
> +    }
> +
> +    /**
> +     * @param node
> +     * @param attr
> +     * @return The value of the given attribute, or null if not present.
> +     */
> +    public static String getAttribute(Node node, String attr)
> +    {
> +        return getAttribute(node, attr, null);
> +    }
> +
> +    /**
> +     * Retrieves an attribute as a boolean.
> +     *
> +     * @param node
> +     * @param attr
> +     * @param def
> +     * @return True if the attribute exists and is not equal to "false"
> +     *    false if equal to "false", and def if not present.
> +     */
> +    public static boolean getBoolAttribute(Node node, String attr,
> boolean def)
> +    {
> +        String value = getAttribute(node, attr);
> +        if (value == null)
> +        {
> +            return def;
> +        }
> +        return Boolean.parseBoolean(value);
> +    }
> +
> +    /**
> +     * @param node
> +     * @param attr
> +     * @return True if the attribute exists and is not equal to "false"
> +     *    false otherwise.
> +     */
> +    public static boolean getBoolAttribute(Node node, String attr)
> +    {
> +        return getBoolAttribute(node, attr, false);
> +    }
> +
> +    /**
> +     * @return An attribute coerced to an integer.
> +     */
> +    public static int getIntAttribute(Node node, String attr, int def)
> +    {
> +        String value = getAttribute(node, attr);
> +        if (value == null)
> +        {
> +            return def;
> +        }
> +        try
> +        {
> +            return Integer.parseInt(value);
> +        }
> +        catch (NumberFormatException e)
> +        {
> +            return def;
> +        }
> +    }
> +
> +    /**
> +     * @return An attribute coerced to an integer.
> +     */
> +    public static int getIntAttribute(Node node, String attr)
> +    {
> +        return getIntAttribute(node, attr, 0);
> +    }
> +
> +    /**
> +     * Attempts to parse the input xml into a single element.
> +     * @param xml
> +     * @return The document object
> +     */
> +    public static Element parse(Reader xml)
> +    {
> +        Element ret = null;
> +        DocumentBuilder builder = getDocumentBuilder();
> +        try
> +        {
> +            ret = builder.parse(new InputSource(xml)).
> getDocumentElement();
> +            releaseBuilder(builder);
> +            return ret;
> +        }
> +        catch(Exception e)
> +        {
> +            LOGGER.error("could not parse given xml", e);
> +        }
> +        finally
> +        {
> +            releaseBuilder(builder);
> +        }
> +        return ret;
> +    }
> +
> +    /**
> +     * Attempts to parse the input xml into a single element.
> +     * @param xml
> +     * @return The document object
> +     */
> +    public static Element parse(String xml)
> +    {
> +        return parse(new StringReader(xml));
> +    }
> +
> +    public static NodeList search(String xpath, Node context)
> +    {
> +        NodeList ret = null;
> +        try
> +        {
> +            XPath xp = XPathFactory.newInstance().newXPath();
> +            XPathExpression exp = xp.compile(xpath);
> +            ret = (NodeList)exp.evaluate(context,
> XPathConstants.NODESET);
> +        }
> +        catch (XPathExpressionException xpe)
> +        {
> +            LOGGER.error("could not process xpath expression {}", xpath,
> xpe);
> +        }
> +        return ret;
> +    }
> +
> +    /**
> +     * <p>Builds the xpath expression for a node (tries to use id/name
> nodes when possible to get a unique path)</p>
> +     *
> +     */
> +    // (borrow from http://stackoverflow.com/questions/5046174/get-xpath-
> from-the-org-w3c-dom-node )
> +    public static String nodePath(Node n)
> +    {
> +        // abort early
> +        if (null == n)
> +            return null;
> +
> +        // declarations
> +        Node parent = null;
> +        Stack<Node> hierarchy = new Stack<Node>();
> +        StringBuffer buffer = new StringBuffer('/');
> +
> +        // push element on stack
> +        hierarchy.push(n);
> +
> +        switch (n.getNodeType()) {
> +            case Node.ATTRIBUTE_NODE:
> +                parent = ((Attr) n).getOwnerElement();
> +                break;
> +            case Node.ELEMENT_NODE:
> +                parent = n.getParentNode();
> +                break;
> +            case Node.DOCUMENT_NODE:
> +                parent = n.getParentNode();
> +                break;
> +            default:
> +                throw new IllegalStateException("Unexpected Node type" +
> n.getNodeType());
> +        }
> +
> +        while (null != parent && parent.getNodeType() !=
> Node.DOCUMENT_NODE) {
> +            // push on stack
> +            hierarchy.push(parent);
> +
> +            // get parent of parent
> +            parent = parent.getParentNode();
> +        }
> +
> +        // construct xpath
> +        Object obj = null;
> +        while (!hierarchy.isEmpty() && null != (obj = hierarchy.pop())) {
> +            Node node = (Node) obj;
> +            boolean handled = false;
> +
> +            if (node.getNodeType() == Node.ELEMENT_NODE)
> +            {
> +                Element e = (Element) node;
> +
> +                // is this the root element?
> +                if (buffer.length() == 1)
> +                {
> +                    // root element - simply append element name
> +                    buffer.append(node.getNodeName());
> +                }
> +                else
> +                {
> +                    // child element - append slash and element name
> +                    buffer.append("/");
> +                    buffer.append(node.getNodeName());
> +
> +                    if (node.hasAttributes())
> +                    {
> +                        // see if the element has a name or id attribute
> +                        if (e.hasAttribute("id"))
> +                        {
> +                            // id attribute found - use that
> +                            buffer.append("[@id='" + e.getAttribute("id")
> + "']");
> +                            handled = true;
> +                        }
> +                        else if (e.hasAttribute("name"))
> +                        {
> +                            // name attribute found - use that
> +                            buffer.append("[@name='" +
> e.getAttribute("name") + "']");
> +                            handled = true;
> +                        }
> +                    }
> +
> +                    if (!handled)
> +                    {
> +                        // no known attribute we could use - get sibling
> index
> +                        int prev_siblings = 1;
> +                        Node prev_sibling = node.getPreviousSibling();
> +                        while (null != prev_sibling)
> +                        {
> +                            if (prev_sibling.getNodeType() ==
> node.getNodeType())
> +                            {
> +                                if (prev_sibling.getNodeName().
> equalsIgnoreCase(
> +                                    node.getNodeName()))
> +                                {
> +                                    prev_siblings++;
> +                                }
> +                            }
> +                            prev_sibling = prev_sibling.
> getPreviousSibling();
> +                        }
> +                        buffer.append("[" + prev_siblings + "]");
> +                    }
> +                }
> +            }
> +            else if (node.getNodeType() == Node.ATTRIBUTE_NODE)
> +            {
> +                buffer.append("/@");
> +                buffer.append(node.getNodeName());
> +            }
> +        }
> +        // return buffer
> +        return buffer.toString();
> +    }
> +
> +    public static String nodeToString(Node node)
> +
> +    {
> +        StringWriter sw = new StringWriter();
> +        try
> +        {
> +            Transformer t = TransformerFactory.
> newInstance().newTransformer();
> +            t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
> +            t.setOutputProperty(OutputKeys.INDENT, "no");
> +            /* CB - Since everything is already stored as strings in
> memory why shoud an encoding be required here? */
> +            t.setOutputProperty(OutputKeys.ENCODING,
> RuntimeConstants.ENCODING_DEFAULT);
> +            t.transform(new DOMSource(node), new StreamResult(sw));
> +        }
> +        catch (TransformerException te)
> +        {
> +            LOGGER.error("could not convert XML node to string", te);
> +        }
> +        return sw.toString();
> +    }
> +
> +    public static boolean isXmlMimeType(String mimeType)
> +    {
> +        return mimeType != null &&
> +            (
> +                "text/xml".equals(mimeType) ||
> +                "application/xml".equals(mimeType) ||
> +                mimeType.endsWith("+xml")
> +            );
> +    }
> +}
>
> Added: velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportSupport.java
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-generic/src/main/java/org/apache/velocity/tools/
> generic/ImportSupport.java?rev=1776916&view=auto
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportSupport.java (added)
> +++ velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportSupport.java Mon Jan  2 10:49:55
> 2017
> @@ -0,0 +1,588 @@
> +package org.apache.velocity.tools.generic;
> +
> +/*
> + * 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.
> + */
> +
> +import org.apache.velocity.runtime.RuntimeConstants;
> +import org.apache.velocity.tools.ClassUtils;
> +import org.apache.velocity.tools.ConversionUtils;
> +import org.apache.velocity.tools.Scope;
> +import org.apache.velocity.tools.config.InvalidScope;
> +
> +import java.io.BufferedReader;
> +import java.io.File;
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.io.InputStreamReader;
> +import java.io.Reader;
> +import java.io.UnsupportedEncodingException;
> +import java.net.HttpURLConnection;
> +import java.net.URL;
> +import java.net.URLConnection;
> +
> +/**
> + * <p>Provides methods to import arbitrary local or remote resources as
> strings, generic version.</p>
> + * <p>Based on ImportSupport from the JSTL taglib by Shawn Bayern</p>
> + *
> + * @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
> + * @author Claude Brisson
> + * @since VelocityTools 3.0
> + * @version $$
> + */
> +@InvalidScope({Scope.APPLICATION, Scope.SESSION, Scope.REQUEST}) /* this
> tool is not meant to be used directly*/
> +public class ImportSupport extends SafeConfig
> +{
> +    protected static final String VALID_SCHEME_CHARS =
> +        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567
> 89+.-";
> +
> +    /** Configuration key for XmlTool and JsonTool, used to specify a
> local resource
> +     */
> +    public static final String RESOURCE_KEY = "resource";
> +
> +    /** Configuration key for ImportTool, XmlTool and JsonTool, used to
> specify a local or remote source URL
> +     */
> +    public static final String URL_KEY = "url";
> +
> +    //**********************************************************
> ***********
> +    // URL importation logic
> +
> +    /*
> +     * Overall strategy:  we have two entry points, acquireString() and
> +     * acquireReader().  The latter passes data through unbuffered if
> +     * possible (but note that it is not always possible -- specifically
> +     * for cases where we must use the RequestDispatcher.  The remaining
> +     * methods handle the common.core logic of loading either a URL or a
> local
> +     * resource.
> +     *
> +     * We consider the 'natural' form of remote URLs to be Readers and
> +     * local URLs to be Strings.  Thus, to avoid doing extra work,
> +     * acquireString() and acquireReader() delegate to one another as
> +     * appropriate.  (Perhaps I could have spelled things out more
> clearly,
> +     * but I thought this implementation was instructive, not to mention
> +     * somewhat cute...)
> +     *
> +     * CB: Changes with VelocityTools 3.0 implementation: ImportSupport
> is now splitted in two classes,
> +     * o.a.v.tools.generic.ImportSupport and o.a.v.tools.view.ViewImportSupport
> inheriting the former one.
> +     * In the generic version, only remote urls are supported, while the
> view version will work as aforementioned.
> +     */
> +
> +    /**
> +     * configure import support
> +     * @param values
> +     */
> +    protected void configure(ValueParser values)
> +    {
> +        super.configure(values);
> +    }
> +
> +    /**
> +     *
> +     * @param url the URL resource to return as string
> +     * @return the URL resource as string
> +     * @throws IOException
> +     */
> +    public String acquireString(String url) throws IOException
> +    {
> +        getLog().debug("acquire URL {}", url);
> +        if (isRemoteURL(url))
> +        {
> +            return acquireRemoteURLString(url);
> +        }
> +        else
> +        {
> +            return acquireLocalURLString(url);
> +        }
> +    }
> +
> +    /**
> +     * Aquire the content of a remote URL.
> +     * @param url remote URL
> +     * @return the URL resource as string
> +     * @throws IOException
> +     */
> +    protected String acquireRemoteURLString(String url) throws IOException
> +    {
> +        // delegate to our peer
> +        BufferedReader r = null;
> +        try
> +        {
> +            r = new BufferedReader(acquireRemoteURLReader(url));
> +            StringBuilder sb = new StringBuilder();
> +            int i;
> +            // under JIT, testing seems to show this simple loop is as
> fast
> +            // as any of the alternatives
> +            while ((i = r.read()) != -1)
> +            {
> +                sb.append((char)i);
> +            }
> +            return sb.toString();
> +        }
> +        finally
> +        {
> +            if(r != null)
> +            {
> +                try
> +                {
> +                    r.close();
> +                }
> +                catch (IOException ioe)
> +                {
> +                    getLog().error("Could not close reader.", ioe);
> +                }
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Aquire the content of a local URL.
> +     * @param url local URL
> +     * @return the URL resource as string
> +     * @throws IOException
> +     */
> +    protected String acquireLocalURLString(String url) throws IOException
> +    {
> +        throw new IOException("Only remote URLs are supported");
> +    }
> +
> +    /**
> +     * Acquire a reader to an URL
> +     * @param url the URL to read
> +     * @return a Reader for the InputStream created from the supplied URL
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    public Reader acquireReader(String url) throws IOException
> +    {
> +        getLog().debug("acquire URL {}", url);
> +        if (isRemoteURL(url))
> +        {
> +            return acquireRemoteURLReader(url);
> +        }
> +        else
> +        {
> +            return acquireLocalURLReader(url);
> +        }
> +    }
> +
> +    /**
> +     * Acquire a reader to a remote URL
> +     * @param url the URL to read
> +     * @return a Reader for the InputStream created from the supplied URL
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    protected Reader acquireRemoteURLReader(String url) throws
> IOException
> +    {
> +        // remote URL
> +        URLConnection uc = null;
> +        HttpURLConnection huc = null;
> +        InputStream i = null;
> +
> +        try
> +        {
> +            // handle remote URLs ourselves, using java.net.URL
> +            URL u = ConversionUtils.toURL(url);
> +            uc = u.openConnection();
> +            i = uc.getInputStream();
> +
> +            // check response code for HTTP URLs, per spec,
> +            if (uc instanceof HttpURLConnection)
> +            {
> +                huc = (HttpURLConnection)uc;
> +
> +                int status = huc.getResponseCode();
> +                if (status < 200 || status > 299)
> +                {
> +                    throw new IOException(status + " " + url);
> +                }
> +            }
> +
> +            // okay, we've got a stream; encode it appropriately
> +            Reader r = null;
> +            String charSet;
> +
> +            // charSet extracted according to RFC 2045, section 5.1
> +            String contentType = uc.getContentType();
> +            if (contentType != null)
> +            {
> +                charSet = ImportSupport.getContentTypeAttribute(contentType,
> "charset");
> +                if (charSet == null)
> +                {
> +                    charSet = RuntimeConstants.ENCODING_DEFAULT;
> +                }
> +            }
> +            else
> +            {
> +                charSet = RuntimeConstants.ENCODING_DEFAULT;
> +            }
> +
> +            try
> +            {
> +                r = new InputStreamReader(i, charSet);
> +            }
> +            catch (UnsupportedEncodingException ueex)
> +            {
> +                r = new InputStreamReader(i, RuntimeConstants.ENCODING_
> DEFAULT);
> +            }
> +
> +            if (huc == null)
> +            {
> +                return r;
> +            }
> +            else
> +            {
> +                return new SafeClosingHttpURLConnectionReader(r, huc);
> +            }
> +        }
> +        catch (IOException ex)
> +        {
> +            if (i != null)
> +            {
> +                try
> +                {
> +                    i.close();
> +                }
> +                catch (IOException ioe)
> +                {
> +                    getLog().error("Could not close InputStream", ioe);
> +                }
> +            }
> +
> +            if (huc != null)
> +            {
> +                huc.disconnect();
> +            }
> +            throw new IOException("Problem accessing the remote URL \""
> +                + url + "\". " + ex);
> +        }
> +        catch (RuntimeException ex)
> +        {
> +            if (i != null)
> +            {
> +                try
> +                {
> +                    i.close();
> +                }
> +                catch (IOException ioe)
> +                {
> +                    getLog().error("Could not close InputStream", ioe);
> +                }
> +            }
> +
> +            if (huc != null)
> +            {
> +                huc.disconnect();
> +            }
> +            // because the spec makes us
> +            throw new IOException("Problem accessing the remote URL \"" +
> url + "\" :" + ex.getMessage(), ex);
> +        }
> +    }
> +
> +    /**
> +     * Acquire a reader to a local URL - non applicable to the generic
> version of ImportSupport
> +     * @param url the URL to read
> +     * @return a Reader for the InputStream created from the supplied URL
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    protected Reader acquireLocalURLReader(String url) throws  IOException
> +    {
> +        throw new IOException("Only remote URLs are supported");
> +    }
> +
> +    protected static class SafeClosingHttpURLConnectionReader extends
> Reader
> +    {
> +        private final HttpURLConnection huc;
> +        private final Reader wrappedReader;
> +
> +        SafeClosingHttpURLConnectionReader(Reader r, HttpURLConnection
> huc)
> +        {
> +            this.wrappedReader = r;
> +            this.huc = huc;
> +        }
> +
> +        public void close() throws IOException
> +        {
> +            if(null != huc)
> +            {
> +                huc.disconnect();
> +            }
> +
> +            wrappedReader.close();
> +        }
> +
> +        // Pass-through methods.
> +        public void mark(int readAheadLimit) throws IOException
> +        {
> +            wrappedReader.mark(readAheadLimit);
> +        }
> +
> +        public boolean markSupported()
> +        {
> +            return wrappedReader.markSupported();
> +        }
> +
> +        public int read() throws IOException
> +        {
> +            return wrappedReader.read();
> +        }
> +
> +        public int read(char[] buf) throws IOException
> +        {
> +            return wrappedReader.read(buf);
> +        }
> +
> +        public int read(char[] buf, int off, int len) throws IOException
> +        {
> +            return wrappedReader.read(buf, off, len);
> +        }
> +
> +        public boolean ready() throws IOException
> +        {
> +            return wrappedReader.ready();
> +        }
> +
> +        public void reset() throws IOException
> +        {
> +            wrappedReader.reset();
> +        }
> +
> +        public long skip(long n) throws IOException
> +        {
> +            return wrappedReader.skip(n);
> +        }
> +    }
> +
> +    //**********************************************************
> ***********
> +    // Public utility methods
> +
> +    /**
> +     * Returns whether an URL is remote or local
> +     *
> +     * @param url the url to check out
> +     * @return wether the URL is remote
> +     */
> +    public static boolean isRemoteURL(String url)
> +    {
> +        return getProtocol(url) == null;
> +    }
> +
> +    /**
> +     * Returns protocol, or null for a local URL
> +     *
> +     * @param url the url to check out
> +     * @return found protocol or null for a local URL
> +     */
> +    public static String getProtocol(String url)
> +    {
> +        // a null URL is not remote, by our definition
> +        if (url == null)
> +        {
> +            return null;
> +        }
> +
> +        // do a fast, simple check first
> +        int colonPos;
> +        if ((colonPos = url.indexOf(':')) == -1)
> +        {
> +            return null;
> +        }
> +
> +        // if we DO have a colon, make sure that every character
> +        // leading up to it is a valid scheme character
> +        for (int i = 0; i < colonPos; i++)
> +        {
> +            if (VALID_SCHEME_CHARS.indexOf(url.charAt(i)) == -1)
> +            {
> +                return null;
> +            }
> +        }
> +        // if so, we've got a remote url
> +        return url.substring(0, colonPos);
> +    }
> +
> +    /**
> +     * Get the value associated with a content-type attribute.
> +     * Syntax defined in RFC 2045, section 5.1.
> +     *
> +     * @param input the string containing the attributes
> +     * @param name the name of the content-type attribute
> +     * @return the value associated with a content-type attribute
> +     */
> +    public static String getContentTypeAttribute(String input, String
> name)
> +    {
> +        int begin;
> +        int end;
> +        int index = input.toUpperCase().indexOf(name.toUpperCase());
> +        if (index == -1)
> +        {
> +            return null;
> +        }
> +        index = index + name.length(); // positioned after the attribute
> name
> +        index = input.indexOf('=', index); // positioned at the '='
> +        if (index == -1)
> +        {
> +            return null;
> +        }
> +        index += 1; // positioned after the '='
> +        input = input.substring(index).trim();
> +
> +        if (input.charAt(0) == '"')
> +        {
> +            // attribute value is a quoted string
> +            begin = 1;
> +            end = input.indexOf('"', begin);
> +            if (end == -1)
> +            {
> +                return null;
> +            }
> +        }
> +        else
> +        {
> +            begin = 0;
> +            end = input.indexOf(';');
> +            if (end == -1)
> +            {
> +                end = input.indexOf(' ');
> +            }
> +            if (end == -1)
> +            {
> +                end = input.length();
> +            }
> +        }
> +        return input.substring(begin, end).trim();
> +    }
> +
> +    //**********************************************************
> ***********
> +    // Fetch local resource
> +
> +    /**
> +     * Fetch a local resource, first trying with a file (or a webapp
> resource for the view flavor)
> +     * then with a classpath entry.
> +     * @param resource the resource to read
> +     * @return the content of the resource
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    public String getResourceString(String resource)
> +    {
> +        String ret = null;
> +        try
> +        {
> +            Reader rawReader = getResourceReader(resource);
> +            if (rawReader != null)
> +            {
> +                BufferedReader reader = new BufferedReader(rawReader);
> +                StringBuilder sb = new StringBuilder();
> +                int i;
> +                // under JIT, testing seems to show this simple loop is
> as fast
> +                // as any of the alternatives
> +                while ((i = reader.read()) != -1)
> +                {
> +                    sb.append((char) i);
> +                }
> +                ret = sb.toString();
> +            }
> +        }
> +        catch (IOException ioe)
> +        {
> +            getLog().error("could not load resource {}", resource, ioe);
> +        }
> +        return ret;
> +    }
> +
> +    /**
> +     * Get a reader of a local resource, first trying with a file (or a
> webapp resource for the view flavor)
> +     * then with a classpath entry.
> +     * @param resource the resource to read
> +     * @return a reader of the resource
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    public Reader getResourceReader(String resource)
> +    {
> +        getLog().debug("get resource {}", resource);
> +        URL url = null;
> +        Reader reader = null;
> +        try
> +        {
> +            url = getFileResource(resource);
> +            if (url == null)
> +            {
> +                url = getClasspathResource(resource);
> +            }
> +            if (url != null)
> +            {
> +                URLConnection uc = url.openConnection();
> +                InputStream is = uc.getInputStream();
> +                String charSet;
> +                // charSet extracted according to RFC 2045, section 5.1
> +                String contentType = uc.getContentType();
> +                if (contentType != null)
> +                {
> +                    charSet = ImportSupport.getContentTypeAttribute(contentType,
> "charset");
> +                    if (charSet == null)
> +                    {
> +                        charSet = RuntimeConstants.ENCODING_DEFAULT;
> +                    }
> +                }
> +                else
> +                {
> +                    charSet = RuntimeConstants.ENCODING_DEFAULT;
> +                }
> +                reader = new InputStreamReader(is, charSet);
> +            }
> +        }
> +        catch (Exception e)
> +        {
> +            getLog().error("could not get resource {}", resource, e);
> +        }
> +        return reader;
> +    }
> +
> +    /**
> +     * Overridable local file URL builder.
> +     * @param resource the resource to read
> +     * @return the content of the resource
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    protected URL getFileResource(String resource) throws Exception
> +    {
> +        URL url = null;
> +        File file = new File(resource);
> +        if (file.exists() && file.isFile() && file.canRead())
> +        {
> +            url = file.toURI().toURL();
> +        }
> +        return url;
> +    }
> +
> +    /**
> +     * Classpath entry URL builder
> +     * @param resource the resource to read
> +     * @return the content of the resource
> +     * @throws IOException
> +     * @throws java.lang.Exception
> +     */
> +    protected URL getClasspathResource(String resource) throws Exception
> +    {
> +        return ClassUtils.getResource(resource, this);
> +    }
> +}
>
> Copied: velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportTool.java (from r1770862,
> velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/ImportTool.java)
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-generic/src/main/java/org/apache/velocity/tools/
> generic/ImportTool.java?p2=velocity/tools/trunk/velocity-
> tools-generic/src/main/java/org/apache/velocity/tools/
> generic/ImportTool.java&p1=velocity/tools/trunk/velocity-
> tools-view/src/main/java/org/apache/velocity/tools/view/
> ImportTool.java&r1=1770862&r2=1776916&rev=1776916&view=diff
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-view/src/main/java/org/
> apache/velocity/tools/view/ImportTool.java (original)
> +++ velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/ImportTool.java Mon Jan  2 10:49:55 2017
> @@ -1,4 +1,4 @@
> -package org.apache.velocity.tools.view;
> +package org.apache.velocity.tools.generic;
>
>  /*
>   * Licensed to the Apache Software Foundation (ASF) under one
> @@ -22,12 +22,10 @@ package org.apache.velocity.tools.view;
>  import org.apache.velocity.tools.Scope;
>  import org.apache.velocity.tools.config.DefaultKey;
>  import org.apache.velocity.tools.config.ValidScope;
> -import org.apache.velocity.tools.view.ImportSupport;
>
>  /**
> - * General-purpose text-importing view tool for templates.
> - * <p>Usage:<br />
> - * Just call $import.read("http://www.foo.com/bleh.jsp?sneh=bar") to
> insert the contents of the named
> + * General-purpose text-importing tool for templates.
> + * <p>Usage: just call $import.read("http://www.foo.com/bleh.jsp?sneh=bar")
> to insert the contents of the named
>   * resource into the template.
>   * </p>
>   * <p><pre>
> @@ -40,39 +38,92 @@ import org.apache.velocity.tools.view.Im
>   * </pre></p>
>   *
>   * @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
> - * @since VelocityTools 2.0
> - * @version $Revision$ $Date$
> + * @since VelocityTools 3.0
> + * @version $Id$
>   */
>
>  @DefaultKey("import")
>  @ValidScope(Scope.REQUEST)
> -public class ImportTool extends ImportSupport
> +public class ImportTool extends SafeConfig
>  {
>      /**
> +     * ImportSupport utility which provides underlying i/o
> +     */
> +    protected ImportSupport importSupport = null;
> +
> +    /**
> +     * Importsupport initialization
> +     * @param config
> +     */
> +    protected void initializeImportSupport(ValueParser config)
> +    {
> +        importSupport = new ImportSupport();
> +        importSupport.configure(config);
> +    }
> +
> +    /**
> +     * Configuration
> +     * @param values
> +     */
> +    protected void configure(ValueParser values)
> +    {
> +        initializeImportSupport(values);
> +    }
> +
> +    /**
> +     * Returns the supplied resource rendered as a String.
> +     *
> +     * @param resource the URL to import
> +     * @return the URL as a string
> +     */
> +    public String read(String resource)
> +    {
> +        if (resource == null)
> +        {
> +            getLog().warn("resource is null!");
> +            return null;
> +        }
> +        if (resource.length() == 0)
> +        {
> +            getLog().warn("resource is empty string!");
> +            return null;
> +        }
> +        try
> +        {
> +            return importSupport.getResourceString(resource);
> +        }
> +        catch (Exception ex)
> +        {
> +            getLog().error("Exception while getting '{}'", resource, ex);
> +            return null;
> +        }
> +    }
> +
> +    /**
>       * Returns the supplied URL rendered as a String.
>       *
> -     * @param obj the URL to import
> +     * @param url the URL to import
>       * @return the URL as a string
>       */
> -    public String read(Object obj) {
> -        if (obj == null)
> +    public String fetch(String url)
> +    {
> +        if (url == null)
>          {
> -            getLog().warn("ImportTool.read(): url is null!");
> +            getLog().warn("URL is null!");
>              return null;
>          }
> -        String url = String.valueOf(obj).trim();
>          if (url.length() == 0)
>          {
> -            getLog().warn("ImportTool.read(): url is empty string!");
> +            getLog().warn("URL is empty string!");
>              return null;
>          }
>          try
>          {
> -            return acquireString(url);
> +            return importSupport.acquireString(url);
>          }
>          catch (Exception ex)
>          {
> -            getLog().error("ImportTool.read(): Exception while aquiring
> '{}'", url, ex);
> +            getLog().error("Exception while acquiring '{}'", url, ex);
>              return null;
>          }
>      }
>
> Added: velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/JsonTool.java
> URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-
> tools-generic/src/main/java/org/apache/velocity/tools/
> generic/JsonTool.java?rev=1776916&view=auto
> ============================================================
> ==================
> --- velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/JsonTool.java (added)
> +++ velocity/tools/trunk/velocity-tools-generic/src/main/java/
> org/apache/velocity/tools/generic/JsonTool.java Mon Jan  2 10:49:55 2017
> @@ -0,0 +1,372 @@
> +package org.apache.velocity.tools.generic;
> +
> +/*
> + * 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.
> + */
> +
> +import java.io.BufferedReader;
> +import java.io.InputStreamReader;
> +import java.io.Reader;
> +import java.io.StringReader;
> +import java.net.URL;
> +import java.util.Iterator;
> +import java.util.Set;
> +
> +import org.apache.velocity.tools.ConversionUtils;
> +import org.apache.velocity.tools.XmlUtils;
> +import org.json.JSONArray;
> +import org.json.JSONObject;
> +import org.json.JSONTokener;
> +
> +import org.apache.velocity.tools.Scope;
> +import org.apache.velocity.tools.config.DefaultKey;
> +import org.apache.velocity.tools.config.InvalidScope;
> +import org.w3c.dom.Document;
> +import org.w3c.dom.Node;
> +
> +/**
> + * <p>Tool which can parse a JSON file.</o>
> + * <p>Usage:</p>
> + * <p>
> + *     <ul>
> + *         <li>$json.parse(<i>json string</i>)</li>
> + *         <li>$json.read(<i>file or classpath resource</i>)</li>
> + *         <li>$json.fetch(<i>URL</i>)</li>
> + *     </ul>
> + * </p>
> + * <p>Configuration parameters:</p>
> + * <p>
> + *     <ul>
> + *         <li><code>resource</code>=<i>file or classpath
> resource</i></li>
> + *         <li><code>source</code>=<i>URL</i></li>
> + *     </ul>
> + * </p>
> + * <p>
> + *     <pre>
> + * Example configuration:
> + * &lt;tools&gt;
> + *   &lt;toolbox scope="request"&gt;
> + *     &lt;tool class="org.apache.velocity.tools.generic.JsonTool"
> + *              key="foo" resource="doc.xml"/&gt;
> + *   &lt;/toolbox&gt;
> + * &lt;/tools&gt;
> + *
> + *     </pre>
> + * </p>
> + * @author Claude Brisson
> + * @since VelocityTools 3.0
> + * @version $Id:$
> + */
> +
> +// JSONObject isn't (yet?) Serializable, so session scope is invalid
> +@DefaultKey("json")
> +@InvalidScope(Scope.SESSION)
> +public class JsonTool extends ImportSupport implements Iterable
> +{
> +    /**
> +     * ImportSupport utility which provides underlying i/o
> +     */
> +    protected ImportSupport importSupport = null;
> +
> +    /**
> +     * ImportSupport initialization
> +     * @param config
> +     */
> +    protected void initializeImportSupport(ValueParser config)
> +    {
> +        importSupport = new ImportSupport();
> +        importSupport.configure(config);
> +    }
> +
> +    /**
> +     * JSONObject content
> +     */
> +    private JSONObject jsonObject = null;
> +
> +    /**
> +     * JSONArray content
> +     */
> +    private JSONArray jsonArray = null;
> +
> +    /**
> +     * Looks for the "file" parameter and automatically uses
> +     * {@link #initJSON(String)} to parse the file (searched in
> filesystem current path and classpath) and set the
> +     * resulting JSON object as the root node for this instance.
> +     */
> +    protected void configure(ValueParser values)
> +    {
> +        super.configure(values);
> +        initializeImportSupport(values);
> +        String resource = values.getString(ImportSupport.RESOURCE_KEY);
> +        if (resource != null)
> +        {
> +            read(resource);
> +        }
> +        else
> +        {
> +            String url = values.getString(ImportSupport.URL_KEY);
> +            if (url != null)
> +            {
> +                fetch(url);
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Initialize JSON content from a string.
> +     * @param json
> +     */
> +    protected void initJSON(String json)
> +    {
> +        if (json != null)
> +        {
> +            initJSON(new StringReader(json));
> +        }
> +    }
> +
> +    /**
> +     * Initialize JSON content from a reader.
> +     * @param reader
> +     */
> +    protected void initJSON(Reader reader)
> +    {
> +        try
> +        {
> +            final int lookahead = 100;
> +            int jsonType = 0; // 1 = object, 2 = array
> +            if (!reader.markSupported())
> +            {
> +                reader = new BufferedReader(reader);
> +            }
> +            reader.mark(lookahead);
> +            char buffer[] = new char[lookahead];
> +            int read = reader.read(buffer);
> +            reader.reset();
> +            for (int i = 0; i < read; ++i)
> +            {
> +                switch (buffer[i])
> +                {
> +                    case '{':
> +                        jsonType = 1;
> +                        break;
> +                    case '[':
> +                        jsonType = 2;
> +                        break;
> +                    case ' ':
> +                    case '\t':
> +                    case '\r':
> +                    case '\n':
> +                        break;
> +                    default:
> +                    {
> +                        String msg = "could not pase JSON: invalid
> character at position " + i + ": '" + buffer[i] + "'";
> +                        throw new Exception(msg);
> +                    }
> +                }
> +                if (jsonType != 0)
> +                {
> +                    break;
> +                }
> +            }
> +            switch (jsonType)
> +            {
> +                case 0:
> +                {
> +                    String msg = "could not pase JSON: did not find any
> '{' or '[' in the first " + lookahead + " characters";
> +                    throw new Exception(msg);
> +                }
> +                case 1:
> +                    jsonArray = null;
> +                    jsonObject = new JSONObject(new JSONTokener(reader));
> +                    break;
> +                case 2:
> +                    jsonObject = null;
> +                    jsonArray = new JSONArray(new JSONTokener(reader));
> +            }
> +        }
> +        catch (Exception e)
> +        {
> +            getLog().error("error while setting up JSON source", e);
> +        }
> +    }
> +
> +    /**
> +     * Parses the given JSON string and uses the resulting {@link
> Document}
> +     * as the root {@link Node}.
> +     */
> +    public void parse(String xml) throws Exception
> +    {
> +        if (xml != null)
> +        {
> +            try
> +            {
> +                initJSON(xml);
> +            }
> +            catch (Exception e)
> +            {
> +                getLog().error("could not parse given JSON string", e);
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Reads and parses a local JSON resource file
> +     */
> +    public void read(String resource)
> +    {
> +        if (resource != null)
> +        {
> +            try
> +            {
> +                Reader reader = importSupport.
> getResourceReader(resource);
> +                if (reader != null)
> +                {
> +                    initJSON(reader);
> +                }
> +            }
> +            catch (Exception e)
> +            {
> +                getLog().error("could not read JSON resource {}",
> resource, e);
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Reads and parses a remote or local URL
> +     */
> +    public void fetch(String url)
> +    {
> +        if (url != null)
> +        {
> +            try
> +            {
> +                Reader reader = importSupport.acquireReader(url);
> +                if (reader != null)
> +                {
> +                    initJSON(reader);
> +                }
> +            }
> +            catch (Exception e)
> +            {
> +                getLog().error("could not fetch JSON content from URL
> {}", url, e);
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Get JSON root object.
> +     * @return root object or array
> +     */
> +    public Object root()
> +    {
> +        return jsonObject != null ? jsonObject : jsonArray ;
> +    }
> +
> +    /**
> +     * Get nth element from root json array.
> +     * @param index n
> +     * @return nth element, or null if root object is null or not an array
> +     */
> +    public Object get(int index)
> +    {
> +        return jsonArray == null ? null : jsonArray.get(index);
> +    }
> +
> +    /**
> +     * Get a property from root object
> +     * @param key
> +     * @return property value, or null
> +     */
> +    public Object get(String key)
> +    {
> +        return jsonObject == null ? null : jsonObject.get(key);
> +    }
> +
> +    /**
> +     * Iterate keys of root object.
> +     * @return iterator
> +     */
> +    public Iterator<String> keys()
> +    {
> +        return jsonObject == null ? null : jsonObject.keys();
> +    }
> +
> +    /**
> +     * Get set of root object keys.
> +     * @return
> +     */
> +    public Set<String> keySet()
> +    {
> +        return jsonObject == null ? null : jsonObject.keySet();
> +    }
> +
> +    /**
> +     * Get an iterator. For a root object, returns an iterator over key
> names. For a root array, returns an iterator
> +     * over contained objects.
> +     * @return iterator
> +     */
> +    public Iterator iterator()
> +    {
> +        if (jsonObject != null)
> +        {
> +            return jsonObject. keys();
> +        }
> +        else if (jsonArray != null)
> +        {
> +            return jsonArray.iterator();
> +        }
> +        return null;
> +    }
> +
> +    /**
> +     * Get size of root object or array.
> +     * @return size
> +     */
> +    public int length()
> +    {
> +        return jsonObject == null ? jsonArray == null ? null :
> jsonArray.length() : jsonObject.length();
> +    }
> +
> +    /**
> +     * Get array of root object keys.
> +     * @return array of keys
> +     */
> +    public JSONArray names()
> +    {
> +        return jsonObject == null ? null : jsonObject.names();
> +    }
> +
> +    /**
> +     * Query root object or array using a JSON pointer
> +     * @param jsonPointer
> +     * @return result
> +     */
> +    public Object query(String jsonPointer)
> +    {
> +        return jsonObject == null ? jsonArray == null ? null :
> jsonArray.query(jsonPointer) : jsonObject.query(jsonPointer);
> +    }
> +
> +    /**
> +     * Convert JSON object or array into string
> +     * @return JSON representation of the root object or array
> +     */
> +    public String toString()
> +    {
> +        return jsonObject == null ? jsonArray == null ? "null" :
> jsonArray.toString() : jsonObject.toString();
> +    }
> +}
>
>
>

Re: svn commit: r1776916 [1/2] - in /velocity/tools/trunk: ./ src/changes/ velocity-tools-assembly/ velocity-tools-assembly/src/main/assembly/ velocity-tools-examples/velocity-tools-examples-showcase/ velocity-tools-examples/velocity-tools-examples-showcas...

Posted by Claude Brisson <cl...@renegat.net>.
On 02/01/2017 15:13, Nathan Bubna wrote:

> Wow. Sounds great, Claude!

Thanks!

>
> generic.XmlTool and view.XmlTool expose parse(String xml), read(String
> xmlResource) and fetch(String url) methods, based on the ImportSupport and
> ViewImportSupport APIs. Both accept the 'resource=<resource_path>' and
> 'source=<url>' configuration parameters.
>
> generic.JsonTool and view.JsonTool expose parse(String json), read(String
> jsonResource) and fetch(String url) methods, based on the ImportSupport and
> ViewImportSupport APIs. Both accept the 'resource=<resource_path>' and
> 'source=<url>' configuration parameters.
>
Errata: it's 'resource=<resource_path>' and 'url=<url>'


   Claude


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@velocity.apache.org
For additional commands, e-mail: dev-help@velocity.apache.org