Repository: nifi
Updated Branches:
  refs/heads/master 557d6365b -> bb2431270


NIFI-2142 Added Guava cache for XSLT stylesheets in TransformXml

This closes #609

Signed-off-by: jpercivall <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/bb243127
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/bb243127
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/bb243127

Branch: refs/heads/master
Commit: bb243127092b9524d881c2c0e75566cda0285c00
Parents: 557d636
Author: Joey Frazee <[email protected]>
Authored: Tue Jun 28 15:29:43 2016 -0700
Committer: jpercivall <[email protected]>
Committed: Wed Aug 10 11:13:30 2016 -0400

----------------------------------------------------------------------
 .../nifi-standard-processors/pom.xml            |  4 +
 .../nifi/processors/standard/TransformXml.java  | 87 +++++++++++++++++++-
 .../processors/standard/TestTransformXml.java   | 47 +++++++++--
 .../test/resources/TestTransformXml/math.html   | 14 ++--
 .../test/resources/TestTransformXml/math.xsl    |  8 +-
 .../test/resources/TestTransformXml/tokens.xml  | 28 +++----
 6 files changed, 153 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/bb243127/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
index 433d7e8..67ff74d 100644
--- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
+++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml
@@ -190,6 +190,10 @@ language governing permissions and limitations under the 
License. -->
             <version>0.4.1</version>
         </dependency>
         <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-mock</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/nifi/blob/bb243127/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TransformXml.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TransformXml.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TransformXml.java
index 5c3c23c..e90a3a1 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TransformXml.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/TransformXml.java
@@ -28,7 +28,10 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Templates;
 import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
@@ -41,6 +44,7 @@ import org.apache.nifi.annotation.behavior.SideEffectFree;
 import org.apache.nifi.annotation.behavior.SupportsBatching;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnScheduled;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
@@ -60,6 +64,10 @@ import org.apache.nifi.stream.io.BufferedInputStream;
 import org.apache.nifi.util.StopWatch;
 import org.apache.nifi.util.Tuple;
 
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
 @EventDriven
 @SideEffectFree
 @SupportsBatching
@@ -76,13 +84,43 @@ public class TransformXml extends AbstractProcessor {
             .name("XSLT file name")
             .description("Provides the name (including full path) of the XSLT 
file to apply to the flowfile XML content.")
             .required(true)
+            .expressionLanguageSupported(true)
             .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR)
             .build();
 
+    public static final PropertyDescriptor INDENT_OUTPUT = new 
PropertyDescriptor.Builder()
+            .name("indent-output")
+            .displayName("Indent")
+            .description("Whether or not to indent the output.")
+            .required(true)
+            .defaultValue("true")
+            .allowableValues("true", "false")
+            .addValidator(StandardValidators.BOOLEAN_VALIDATOR)
+            .build();
+
+    public static final PropertyDescriptor CACHE_SIZE = new 
PropertyDescriptor.Builder()
+            .name("cache-size")
+            .displayName("Cache size")
+            .description("Maximum number of stylesheets to cache. Zero 
disables the cache.")
+            .required(true)
+            .defaultValue("10")
+            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
+            .build();
+
+    public static final PropertyDescriptor CACHE_TTL_AFTER_LAST_ACCESS = new 
PropertyDescriptor.Builder()
+            .name("cache-ttl-after-last-access")
+            .displayName("Cache TTL after last access")
+            .description("The cache TTL (time-to-live) or how long to keep 
stylesheets in the cache after last access.")
+            .required(true)
+            .defaultValue("60 secs")
+            .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
+            .build();
+
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
             .description("The FlowFile with transformed content will be routed 
to this relationship")
             .build();
+
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
             .description("If a FlowFile fails processing for any reason (for 
example, the FlowFile is not valid XML), it will be routed to this 
relationship")
@@ -90,11 +128,15 @@ public class TransformXml extends AbstractProcessor {
 
     private List<PropertyDescriptor> properties;
     private Set<Relationship> relationships;
+    private LoadingCache<String, Templates> cache;
 
     @Override
     protected void init(final ProcessorInitializationContext context) {
         final List<PropertyDescriptor> properties = new ArrayList<>();
         properties.add(XSLT_FILE_NAME);
+        properties.add(INDENT_OUTPUT);
+        properties.add(CACHE_SIZE);
+        properties.add(CACHE_TTL_AFTER_LAST_ACCESS);
         this.properties = Collections.unmodifiableList(properties);
 
         final Set<Relationship> relationships = new HashSet<>();
@@ -124,6 +166,35 @@ public class TransformXml extends AbstractProcessor {
                 .build();
     }
 
+    private Templates newTemplates(String path) throws 
TransformerConfigurationException {
+        TransformerFactory factory = TransformerFactory.newInstance();
+        return factory.newTemplates(new StreamSource(path));
+    }
+
+    @OnScheduled
+    public void onScheduled(final ProcessContext context) {
+        final ComponentLog logger = getLogger();
+        final Integer cacheSize = context.getProperty(CACHE_SIZE).asInteger();
+        final Long cacheTTL = 
context.getProperty(CACHE_TTL_AFTER_LAST_ACCESS).asTimePeriod(TimeUnit.SECONDS);
+
+        if (cacheSize > 0) {
+            CacheBuilder cacheBuilder = 
CacheBuilder.newBuilder().maximumSize(cacheSize);
+            if (cacheTTL > 0) {
+                cacheBuilder = cacheBuilder.expireAfterAccess(cacheTTL, 
TimeUnit.SECONDS);
+            }
+
+            cache = cacheBuilder.build(
+               new CacheLoader<String, Templates>() {
+                   public Templates load(String path) throws 
TransformerConfigurationException {
+                       return newTemplates(path);
+                   }
+               });
+        } else {
+            cache = null;
+            logger.warn("Stylesheet cache disabled because cache size is set 
to 0");
+        }
+    }
+
     @Override
     public void onTrigger(final ProcessContext context, final ProcessSession 
session) {
         final FlowFile original = session.get();
@@ -133,17 +204,25 @@ public class TransformXml extends AbstractProcessor {
 
         final ComponentLog logger = getLogger();
         final StopWatch stopWatch = new StopWatch(true);
+        final String xsltFileName = context.getProperty(XSLT_FILE_NAME)
+            .evaluateAttributeExpressions(original)
+            .getValue();
+        final Boolean indentOutput = 
context.getProperty(INDENT_OUTPUT).asBoolean();
 
         try {
             FlowFile transformed = session.write(original, new 
StreamCallback() {
                 @Override
                 public void process(final InputStream rawIn, final 
OutputStream out) throws IOException {
                     try (final InputStream in = new 
BufferedInputStream(rawIn)) {
+                        final Templates templates;
+                        if (cache != null) {
+                            templates = cache.get(xsltFileName);
+                        } else {
+                            templates = newTemplates(xsltFileName);
+                        }
 
-                        File stylesheet = new 
File(context.getProperty(XSLT_FILE_NAME).getValue());
-                        StreamSource styleSource = new 
StreamSource(stylesheet);
-                        TransformerFactory tfactory = new 
net.sf.saxon.TransformerFactoryImpl();
-                        Transformer transformer = 
tfactory.newTransformer(styleSource);
+                        final Transformer transformer = 
templates.newTransformer();
+                        transformer.setOutputProperty(OutputKeys.INDENT, 
(indentOutput ? "yes" : "no"));
 
                         // pass all dynamic properties to the transformer
                         for (final Map.Entry<PropertyDescriptor, String> entry 
: context.getProperties().entrySet()) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/bb243127/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestTransformXml.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestTransformXml.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestTransformXml.java
index 2dbf09f..5ced266 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestTransformXml.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestTransformXml.java
@@ -16,12 +16,15 @@
  */
 package org.apache.nifi.processors.standard;
 
+import java.io.IOException;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
@@ -29,7 +32,6 @@ import java.util.Map;
 import org.apache.nifi.util.MockFlowFile;
 import org.apache.nifi.util.TestRunner;
 import org.apache.nifi.util.TestRunners;
-import org.junit.Ignore;
 
 import org.junit.Test;
 
@@ -59,7 +61,6 @@ public class TestTransformXml {
         original.assertContentEquals("not xml");
     }
 
-    @Ignore("this test fails")
     @Test
     public void testTransformMath() throws IOException {
         final TestRunner runner = TestRunners.newTestRunner(new 
TransformXml());
@@ -72,12 +73,11 @@ public class TestTransformXml {
 
         runner.assertAllFlowFilesTransferred(TransformXml.REL_SUCCESS);
         final MockFlowFile transformed = 
runner.getFlowFilesForRelationship(TransformXml.REL_SUCCESS).get(0);
-        final String transformedContent = new 
String(transformed.toByteArray(), StandardCharsets.UTF_8);
+        final String expectedContent = new 
String(Files.readAllBytes(Paths.get("src/test/resources/TestTransformXml/math.html"))).trim();
 
-        
transformed.assertContentEquals(Paths.get("src/test/resources/TestTransformXml/math.html"));
+        transformed.assertContentEquals(expectedContent);
     }
 
-    @Ignore("this test fails")
     @Test
     public void testTransformCsv() throws IOException {
         final TestRunner runner = TestRunners.newTestRunner(new 
TransformXml());
@@ -108,9 +108,44 @@ public class TestTransformXml {
             runner.assertAllFlowFilesTransferred(TransformXml.REL_SUCCESS);
             final MockFlowFile transformed = 
runner.getFlowFilesForRelationship(TransformXml.REL_SUCCESS).get(0);
             final String transformedContent = new 
String(transformed.toByteArray(), StandardCharsets.ISO_8859_1);
+            final String expectedContent = new 
String(Files.readAllBytes(Paths.get("src/test/resources/TestTransformXml/tokens.xml")));
 
-            
transformed.assertContentEquals(Paths.get("src/test/resources/TestTransformXml/tokens.xml"));
+            transformed.assertContentEquals(expectedContent);
         }
     }
 
+    @Test
+    public void testTransformExpressionLanguage() throws IOException {
+        final TestRunner runner = TestRunners.newTestRunner(new 
TransformXml());
+        runner.setProperty("header", "Test for mod");
+        runner.setProperty(TransformXml.XSLT_FILE_NAME, "${xslt.path}");
+
+        final Map<String, String> attributes = new HashMap<>();
+        attributes.put("xslt.path", 
"src/test/resources/TestTransformXml/math.xsl");
+        
runner.enqueue(Paths.get("src/test/resources/TestTransformXml/math.xml"), 
attributes);
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(TransformXml.REL_SUCCESS);
+        final MockFlowFile transformed = 
runner.getFlowFilesForRelationship(TransformXml.REL_SUCCESS).get(0);
+        final String expectedContent = new 
String(Files.readAllBytes(Paths.get("src/test/resources/TestTransformXml/math.html"))).trim();
+
+        transformed.assertContentEquals(expectedContent);
+    }
+
+    @Test
+    public void testTransformNoCache() throws IOException {
+        final TestRunner runner = TestRunners.newTestRunner(new 
TransformXml());
+        runner.setProperty("header", "Test for mod");
+        runner.setProperty(TransformXml.CACHE_SIZE, "0");
+        runner.setProperty(TransformXml.XSLT_FILE_NAME, 
"src/test/resources/TestTransformXml/math.xsl");
+        
runner.enqueue(Paths.get("src/test/resources/TestTransformXml/math.xml"));
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(TransformXml.REL_SUCCESS);
+        final MockFlowFile transformed = 
runner.getFlowFilesForRelationship(TransformXml.REL_SUCCESS).get(0);
+        final String expectedContent = new 
String(Files.readAllBytes(Paths.get("src/test/resources/TestTransformXml/math.html"))).trim();
+
+        transformed.assertContentEquals(expectedContent);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/bb243127/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.html
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.html
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.html
index 8f58b6e..254d6a3 100755
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.html
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.html
@@ -1,8 +1,8 @@
 <HTML xmlns:xs="http://www.w3.org/2001/XMLSchema";>
-    <H1>Test for mod</H1>
-    <HR>
-    <P>Should say "1": 1</P>
-    <P>Should say "1": 1</P>
-    <P>Should say "-1": -1</P>
-    <P>true</P>
-</HTML>
\ No newline at end of file
+   <H1>Test for mod</H1>
+   <HR>
+   <P>Should say "1": 1</P>
+   <P>Should say "1": 1</P>
+   <P>Should say "-1": -1</P>
+   <P>true</P>
+</HTML>

http://git-wip-us.apache.org/repos/asf/nifi/blob/bb243127/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.xsl
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.xsl
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.xsl
index b17f91b..5c5a0b6 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.xsl
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/math.xsl
@@ -13,10 +13,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<xsl:stylesheet version="2.0" 
-                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; 
+<xsl:stylesheet version="2.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                 xmlns:xs="http://www.w3.org/2001/XMLSchema";>
-               
+
     <xsl:param name="header" />
 
     <xsl:template match="doc">
@@ -33,4 +33,4 @@
             </P>
         </HTML>
     </xsl:template>
-</xsl:stylesheet>
\ No newline at end of file
+</xsl:stylesheet>

http://git-wip-us.apache.org/repos/asf/nifi/blob/bb243127/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/tokens.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/tokens.xml
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/tokens.xml
index 8dfe80b..b7d8efc 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/tokens.xml
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestTransformXml/tokens.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <test release="0.0" id="uuid_0">
-    <event id="uuid_1">
-        <token>1</token>
-        <token>2</token>
-        <token>3</token>
-        <token>4</token>
-        <token>C:\dir$abc</token>
-        <token>6</token>
-        <token>7</token>
-        <token>A,B</token>
-        <token>"don't"</token>
-        <token>2014-05-01T30:23:00Z</token>
-        <token>11</token>
-        <token>12</token>
-    </event>
+   <event id="uuid_1">
+      <token>1</token>
+      <token>2</token>
+      <token>3</token>
+      <token>4</token>
+      <token>C:\dir$abc</token>
+      <token>6</token>
+      <token>7</token>
+      <token>A,B</token>
+      <token>"don't"</token>
+      <token>2014-05-01T30:23:00Z</token>
+      <token>11</token>
+      <token>12</token>
+   </event>
 </test>

Reply via email to