You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by an...@apache.org on 2015/02/03 12:41:34 UTC

[03/17] incubator-brooklyn git commit: add some low-level xslt compound transformer rules

add some low-level xslt compound transformer rules

this will allow refactoring and simplifying the existing mechanisms, and supporting new things like replacing catalogItemId


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/7cd66834
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/7cd66834
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/7cd66834

Branch: refs/heads/master
Commit: 7cd66834d331e9f3e7bf908c207f7f0caf215755
Parents: 55b5f65
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Jan 28 12:25:00 2015 +0000
Committer: Andrea Turli <an...@gmail.com>
Committed: Tue Feb 3 11:25:06 2015 +0100

----------------------------------------------------------------------
 .../rebind/transformer/CompoundTransformer.java | 106 ++++++++++++++-----
 .../transformer/impl/XsltTransformer.java       |   6 +-
 .../recursiveCopyWithExtraRules.xslt            |  32 ++++++
 .../rebind/transformer/xmlReplaceItem.xslt      |  32 ++++++
 .../transformer/CompoundTransformerTest.java    |  81 +++++++++++++-
 5 files changed, 225 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7cd66834/core/src/main/java/brooklyn/entity/rebind/transformer/CompoundTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/transformer/CompoundTransformer.java b/core/src/main/java/brooklyn/entity/rebind/transformer/CompoundTransformer.java
index 80cc1ca..7d1c7c8 100644
--- a/core/src/main/java/brooklyn/entity/rebind/transformer/CompoundTransformer.java
+++ b/core/src/main/java/brooklyn/entity/rebind/transformer/CompoundTransformer.java
@@ -30,6 +30,7 @@ import brooklyn.entity.rebind.transformer.impl.XsltTransformer;
 import brooklyn.mementos.BrooklynMementoRawData;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.text.Strings;
 import brooklyn.util.text.TemplateProcessor;
 
 import com.google.common.annotations.Beta;
@@ -64,34 +65,7 @@ public class CompoundTransformer {
             rawDataTransformers.put(checkNotNull(type, "type"), checkNotNull(val, "val"));
             return this;
         }
-        public Builder renameType(String oldVal, String newVal) {
-            // xstream format for inner classes is like <brooklyn.entity.rebind.transformer.CompoundTransformerTest_-OrigType>
-            oldVal = toXstreamClassnameFormat(oldVal);
-            newVal = toXstreamClassnameFormat(newVal);
-            
-            String xsltTemplate = ResourceUtils.create(this).getResourceAsString("classpath://brooklyn/entity/rebind/transformer/renameType.xslt");
-            String xslt = TemplateProcessor.processTemplateContents(xsltTemplate, ImmutableMap.of("old_val", oldVal, "new_val", newVal));
-            return xsltTransformer(xslt);
-        }
-        public Builder renameClass(String oldVal, String newVal) {
-            // xstream format for inner classes is like <brooklyn.entity.rebind.transformer.CompoundTransformerTest_-OrigType>
-            oldVal = toXstreamClassnameFormat(oldVal);
-            newVal = toXstreamClassnameFormat(newVal);
-            
-            String xsltTemplate = ResourceUtils.create(this).getResourceAsString("classpath://brooklyn/entity/rebind/transformer/renameClass.xslt");
-            String xslt = TemplateProcessor.processTemplateContents(xsltTemplate, ImmutableMap.of("old_val", oldVal, "new_val", newVal));
-            return xsltTransformer(xslt);
-        }
-        public Builder renameField(String clazz, String oldVal, String newVal) {
-            // xstream format for inner classes is like <brooklyn.entity.rebind.transformer.CompoundTransformerTest_-OrigType>
-            clazz = toXstreamClassnameFormat(clazz);
-            oldVal = toXstreamClassnameFormat(oldVal);
-            newVal = toXstreamClassnameFormat(newVal);
-            
-            String xsltTemplate = ResourceUtils.create(this).getResourceAsString("classpath://brooklyn/entity/rebind/transformer/renameField.xslt");
-            String xslt = TemplateProcessor.processTemplateContents(xsltTemplate, ImmutableMap.of("class_name", clazz, "old_val", oldVal, "new_val", newVal));
-            return xsltTransformer(xslt);
-        }
+
         /** registers the given XSLT code to be applied to all persisted {@link BrooklynObjectType}s */
         public Builder xsltTransformer(String xslt) {
             XsltTransformer xsltTransformer = new XsltTransformer(xslt);
@@ -106,7 +80,83 @@ public class CompoundTransformer {
             rawDataTransformer(type, xsltTransformer);
             return this;
         }
+        protected Builder xsltTransformerFromXsltFreemarkerTemplateUrl(String templateUrl, Map<String,String> vars) {
+            String xsltTemplate = ResourceUtils.create(this).getResourceAsString(templateUrl);
+            String xslt = TemplateProcessor.processTemplateContents(xsltTemplate, vars);
+            return xsltTransformer(xslt);
+        }
+        protected Builder xsltTransformerRecursiveCopyWithExtraRules(String ...rules) {
+            String xsltTemplate = ResourceUtils.create(this).getResourceAsString("classpath://brooklyn/entity/rebind/transformer/recursiveCopyWithExtraRules.xslt");
+            String xslt = TemplateProcessor.processTemplateContents(xsltTemplate, ImmutableMap.of("extra_rules", Strings.join(rules, "\n")));
+            return xsltTransformer(xslt);
+        }
+
+        /** Discards and replaces the item at the given XPath.
+         * <p>
+         * For example to replace all occurrences 
+         * of text "foo" inside a tag "Tag1", you can use <code>TagName/text()[.='foo']</code>;
+         * passing <code>bar</code> as the second argument would cause 
+         * <code>&lt;Tag1&gt;foo&lt;/Tag1&gt;</code> to become <code>&lt;Tag1&gt;bar&lt;/Tag1&gt;</code>.
+         * <p>
+         * Note that java class names may require conversion prior to invoking this;
+         * see {@link #toXstreamClassnameFormat(String)}. 
+         */
+        // ie TagName/text()[.='foo'] with 'bar' causes <Tag1>foo</Tag1> to <Tag1>bar</Tag1>
+        public Builder xmlReplaceItem(String xpathToMatch, String newValue) {
+            return xsltTransformerFromXsltFreemarkerTemplateUrl("classpath://brooklyn/entity/rebind/transformer/xmlReplaceItem.xslt",
+                ImmutableMap.of("xpath_to_match", xpathToMatch, "new_val", newValue));
+            // alternatively:
+//            return xsltTransformerRecursiveCopyWithExtraRules(
+//                "<xsl:template match=\""+xpathToMatch+"\">"
+//                    + newValue
+//                + "</xsl:template>");
+        }
+        
+        /** 
+         * Replaces a tag, but while continuing to recurse.
+         */
+        public Builder xmlRenameTag(String xpathToMatch, String newValue) {
+            return xsltTransformerRecursiveCopyWithExtraRules(
+              "<xsl:template match=\""+xpathToMatch+"\">"
+                  + "<"+newValue+">"
+                      + "<xsl:apply-templates select=\"@*|node()\" />"
+                  + "</"+newValue+">"
+              + "</xsl:template>");
+            // alternatively:
+//            xmlReplaceItem(xpathToMatch, "<"+newValue+">"+"<xsl:apply-templates select=\"@*|node()\" />"+"</"+newValue+">") 
+        }
+
+        /** Changes the contents inside a "type" tag:
+         * where the contents match the old value, they are changed to the new value.
+         * <p> 
+         * In brooklyn/xstream, a "type" node typically gives the name of a java or catalog type to be used
+         * when creating an instance. */
+        public Builder renameType(String oldVal, String newVal) {
+            return xsltTransformerFromXsltFreemarkerTemplateUrl("classpath://brooklyn/entity/rebind/transformer/renameType.xslt",
+                ImmutableMap.of("old_val", toXstreamClassnameFormat(oldVal), "new_val", toXstreamClassnameFormat(newVal)));
+        }
+        /** Changes an XML tag matching a given old value:
+         * the tag is changed to the new value.
+         * <p>
+         * In xstream many tags correspond to the java class of an object so this is a way to change 
+         * the java class (or xstream alias) of a persisted instance (or instance inside them). */
+        public Builder renameClass(String oldVal, String newVal) {
+            return xsltTransformerFromXsltFreemarkerTemplateUrl("classpath://brooklyn/entity/rebind/transformer/renameClass.xslt",
+                ImmutableMap.of("old_val", toXstreamClassnameFormat(oldVal), "new_val", toXstreamClassnameFormat(newVal)));
+        }
+        /** Changes an XML tag inside another tag: 
+         * where the outer tag and inner tag match the values given here,
+         * the inner tag is changed to the new value.
+         * <p>
+         * In stream tags corresponding to fields are contained in the tag corresponding to the class name,
+         * so this gives a way to change the name of a field which will be deserialized. */
+        public Builder renameField(String clazz, String oldVal, String newVal) {
+            return xsltTransformerFromXsltFreemarkerTemplateUrl("classpath://brooklyn/entity/rebind/transformer/renameField.xslt",
+                ImmutableMap.of("class_name", toXstreamClassnameFormat(clazz), "old_val", toXstreamClassnameFormat(oldVal), "new_val", toXstreamClassnameFormat(newVal)));
+        }
+
         private String toXstreamClassnameFormat(String val) {
+            // xstream format for inner classes is like <brooklyn.entity.rebind.transformer.CompoundTransformerTest_-OrigType>
             return (val.contains("$")) ? val.replace("$", "_-") : val;
         }
         public CompoundTransformer build() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7cd66834/core/src/main/java/brooklyn/entity/rebind/transformer/impl/XsltTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/transformer/impl/XsltTransformer.java b/core/src/main/java/brooklyn/entity/rebind/transformer/impl/XsltTransformer.java
index feed847..00dfc7f 100644
--- a/core/src/main/java/brooklyn/entity/rebind/transformer/impl/XsltTransformer.java
+++ b/core/src/main/java/brooklyn/entity/rebind/transformer/impl/XsltTransformer.java
@@ -38,14 +38,16 @@ import com.google.common.annotations.Beta;
 public class XsltTransformer implements RawDataTransformer {
 
     private final TransformerFactory factory;
-    private final StreamSource xslt;
+    private final String xsltContent;
 
     public XsltTransformer(String xsltContent) {
         factory = TransformerFactory.newInstance();
-        xslt = new StreamSource(new ByteArrayInputStream(xsltContent.getBytes()));
+        this.xsltContent = xsltContent;
     }
     
     public String transform(String input) throws IOException, URISyntaxException, TransformerException {
+        // stream source is single-use
+        StreamSource xslt = new StreamSource(new ByteArrayInputStream(xsltContent.getBytes()));
         Transformer transformer = factory.newTransformer(xslt);
         
         Source text = new StreamSource(new ByteArrayInputStream(input.getBytes()));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7cd66834/core/src/main/resources/brooklyn/entity/rebind/transformer/recursiveCopyWithExtraRules.xslt
----------------------------------------------------------------------
diff --git a/core/src/main/resources/brooklyn/entity/rebind/transformer/recursiveCopyWithExtraRules.xslt b/core/src/main/resources/brooklyn/entity/rebind/transformer/recursiveCopyWithExtraRules.xslt
new file mode 100644
index 0000000..b0f1eb2
--- /dev/null
+++ b/core/src/main/resources/brooklyn/entity/rebind/transformer/recursiveCopyWithExtraRules.xslt
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+  <xsl:output omit-xml-declaration="yes"/>
+  
+  <xsl:template match="node()|@*">
+    <xsl:copy>
+      <xsl:apply-templates select="node()|@*"/>
+    </xsl:copy>
+  </xsl:template>
+
+${extra_rules}
+
+</xsl:stylesheet>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7cd66834/core/src/main/resources/brooklyn/entity/rebind/transformer/xmlReplaceItem.xslt
----------------------------------------------------------------------
diff --git a/core/src/main/resources/brooklyn/entity/rebind/transformer/xmlReplaceItem.xslt b/core/src/main/resources/brooklyn/entity/rebind/transformer/xmlReplaceItem.xslt
new file mode 100644
index 0000000..8d4acc0
--- /dev/null
+++ b/core/src/main/resources/brooklyn/entity/rebind/transformer/xmlReplaceItem.xslt
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+  <xsl:output omit-xml-declaration="yes"/>
+  
+  <xsl:template match="node()|@*">
+    <xsl:copy>
+      <xsl:apply-templates select="node()|@*"/>
+    </xsl:copy>
+  </xsl:template>
+
+  <xsl:template match="${xpath_to_match}">${new_val}</xsl:template>
+
+</xsl:stylesheet>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7cd66834/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java b/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
index c8cc39d..6e7ab36 100644
--- a/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
@@ -27,16 +27,18 @@ import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
+import brooklyn.entity.rebind.BrooklynObjectType;
 import brooklyn.entity.rebind.PersistenceExceptionHandler;
 import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
 import brooklyn.entity.rebind.RebindExceptionHandler;
-import brooklyn.entity.rebind.RebindOptions;
 import brooklyn.entity.rebind.RebindManager.RebindFailureMode;
+import brooklyn.entity.rebind.RebindOptions;
 import brooklyn.entity.rebind.RebindTestFixtureWithApp;
 import brooklyn.entity.rebind.RebindTestUtils;
 import brooklyn.entity.rebind.RecordingRebindExceptionHandler;
@@ -46,7 +48,6 @@ import brooklyn.entity.rebind.persister.PersistMode;
 import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.management.internal.LocalManagementContext;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.mementos.BrooklynMementoRawData;
 import brooklyn.test.entity.TestApplication;
@@ -55,6 +56,7 @@ import brooklyn.util.os.Os;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 
 @SuppressWarnings("serial")
 public class CompoundTransformerTest extends RebindTestFixtureWithApp {
@@ -71,6 +73,81 @@ public class CompoundTransformerTest extends RebindTestFixtureWithApp {
     }
 
     @Test
+    public void testXmlReplaceItemText() throws Exception {
+        CompoundTransformer transformer = CompoundTransformer.builder()
+            .xmlReplaceItem("Tag1/text()[.='foo']", "bar")
+            .build();
+        assertSingleXmlTransformation(transformer, "<Tag1>foo</Tag1>", "<Tag1>bar</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag1>baz</Tag1>", "<Tag1>baz</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag2>foo</Tag2>", "<Tag2>foo</Tag2>");
+        // works when nested
+        assertSingleXmlTransformation(transformer, "<Tag0><Tag1>foo</Tag1><Tag2/></Tag0>", "<Tag0><Tag1>bar</Tag1><Tag2/></Tag0>");
+        // keeps attributes and other children
+        assertSingleXmlTransformation(transformer, "<Tag1 attr=\"value\">foo</Tag1>", "<Tag1 attr=\"value\">bar</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag1>foo<Tag2/></Tag1>", "<Tag1>bar<Tag2/></Tag1>");
+    }
+    
+    @Test
+    public void testXmlReplaceItemTree() throws Exception {
+        CompoundTransformer transformer = CompoundTransformer.builder()
+            .xmlReplaceItem("Tag1[text()='foo']", "<Tag1>bar</Tag1>")
+            .build();
+        assertSingleXmlTransformation(transformer, "<Tag1>foo</Tag1>", "<Tag1>bar</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag1>baz</Tag1>", "<Tag1>baz</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag2>foo</Tag2>", "<Tag2>foo</Tag2>");
+        // works when nested
+        assertSingleXmlTransformation(transformer, "<Tag0><Tag1>foo</Tag1><Tag2/></Tag0>", "<Tag0><Tag1>bar</Tag1><Tag2/></Tag0>");
+        // this deletes attributes and other children
+        assertSingleXmlTransformation(transformer, "<Tag1 attr=\"value\">foo</Tag1>", "<Tag1>bar</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag1>foo<Tag2/></Tag1>", "<Tag1>bar</Tag1>");
+    }
+    
+    @Test
+    public void testXmlReplaceItemAttribute() throws Exception {
+        // note, the syntax for changing an attribute value is obscure, especially the RHS
+        CompoundTransformer transformer = CompoundTransformer.builder()
+            .xmlReplaceItem("Tag1/@attr[.='foo']", "<xsl:attribute name=\"attr\">bar</xsl:attribute>")
+            .build();
+        assertSingleXmlTransformation(transformer, "<Tag1 attr=\"foo\">foo</Tag1>", "<Tag1 attr=\"bar\">foo</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag1 attr=\"baz\">foo</Tag1>", "<Tag1 attr=\"baz\">foo</Tag1>");
+    }
+    
+    @Test
+    public void testXmlRenameTag() throws Exception {
+        CompoundTransformer transformer = CompoundTransformer.builder()
+            .xmlRenameTag("Tag1[text()='foo']", "Tag2")
+            .build();
+        assertSingleXmlTransformation(transformer, "<Tag1>foo</Tag1>", "<Tag2>foo</Tag2>");
+        assertSingleXmlTransformation(transformer, "<Tag1>baz</Tag1>", "<Tag1>baz</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag2>foo</Tag2>", "<Tag2>foo</Tag2>");
+        // works when nested
+        assertSingleXmlTransformation(transformer, "<Tag0><Tag1>foo</Tag1><Tag2/></Tag0>", "<Tag0><Tag2>foo</Tag2><Tag2/></Tag0>");
+        // keeps attributes and other children
+        assertSingleXmlTransformation(transformer, "<Tag1 attr=\"value\">foo</Tag1>", "<Tag2 attr=\"value\">foo</Tag2>");
+        assertSingleXmlTransformation(transformer, "<Tag1>foo<Tag2/></Tag1>", "<Tag2>foo<Tag2/></Tag2>");
+    }
+    
+    @Test
+    public void testXmlReplaceItemActuallyAlsoRenamingTag() throws Exception {
+        CompoundTransformer transformer = CompoundTransformer.builder()
+            .xmlReplaceItem("Tag1[text()='foo']", "<Tag2><xsl:apply-templates select=\"@*|node()\" /></Tag2>")
+            .build();
+        assertSingleXmlTransformation(transformer, "<Tag1>foo</Tag1>", "<Tag2>foo</Tag2>");
+        assertSingleXmlTransformation(transformer, "<Tag1>baz</Tag1>", "<Tag1>baz</Tag1>");
+        assertSingleXmlTransformation(transformer, "<Tag2>foo</Tag2>", "<Tag2>foo</Tag2>");
+        // works when nested
+        assertSingleXmlTransformation(transformer, "<Tag0><Tag1>foo</Tag1><Tag2/></Tag0>", "<Tag0><Tag2>foo</Tag2><Tag2/></Tag0>");
+        // keeps attributes and other children
+        assertSingleXmlTransformation(transformer, "<Tag1 attr=\"value\">foo</Tag1>", "<Tag2 attr=\"value\">foo</Tag2>");
+        assertSingleXmlTransformation(transformer, "<Tag1>foo<Tag2/></Tag1>", "<Tag2>foo<Tag2/></Tag2>");
+    }
+    
+    protected void assertSingleXmlTransformation(CompoundTransformer transformer, String xmlIn, String xmlOutExpected) throws Exception {
+        String xmlOutActual = Iterables.getOnlyElement( transformer.getRawDataTransformers().get(BrooklynObjectType.ENTITY) ).transform(xmlIn);
+        Assert.assertEquals(xmlOutActual, xmlOutExpected);
+    }
+    
+    @Test
     public void testNoopTransformation() throws Exception {
         CompoundTransformer transformer = CompoundTransformer.builder()
                 .build();