Repository: incubator-brooklyn Updated Branches: refs/heads/master 4a5682511 -> 10bd72e48
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/25d8dc30 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/25d8dc30 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/25d8dc30 Branch: refs/heads/master Commit: 25d8dc309d038437a50b4cbd64fdf66968235fd4 Parents: c4c615b Author: Alex Heneveld <[email protected]> Authored: Wed Jan 28 12:25:00 2015 +0000 Committer: Alex Heneveld <[email protected]> Committed: Thu Jan 29 11:40:24 2015 +0000 ---------------------------------------------------------------------- .../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/25d8dc30/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><Tag1>foo</Tag1></code> to become <code><Tag1>bar</Tag1></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/25d8dc30/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/25d8dc30/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/25d8dc30/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/25d8dc30/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();
