diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/ReflectingStringUnescaper.java b/common/source/java/ch/systemsx/cisd/common/utilities/ReflectingStringUnescaper.java new file mode 100644 index 0000000000000000000000000000000000000000..afea31eab0884ead46b2fd54e00a8d3b21b28b6b --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/utilities/ReflectingStringUnescaper.java @@ -0,0 +1,137 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * Licensed 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 ch.systemsx.cisd.common.utilities; + +import java.lang.reflect.Field; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +/** + * Performs HTML unescaping the string fields of an object. Its implementation is based on + * {@link ReflectingStringEscaper} but there is no support to restrict unescaped fields. + * + * @author Piotr Buczek + */ +public class ReflectingStringUnescaper +{ + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + ReflectingStringUnescaper.class); + + private static int MIN_TIME_LOGGED_MS = 100; + + /** + * Unescape all the string fields on the bean and all fields of objects referred to by the bean. + */ + public static <T> T unescapeDeep(T bean) + { + try + { + long time = System.currentTimeMillis(); + T result = doUnescapeDeep(bean); + long timeSpent = System.currentTimeMillis() - time; + if (timeSpent >= MIN_TIME_LOGGED_MS) + { + operationLog.info((timeSpent) + "ms for unescaping " + + (bean == null ? "" : bean.getClass().getSimpleName())); + } + return result; + } catch (Exception ex) + { + ex.printStackTrace(); + LogUtils.logErrorWithFailingAssertion(operationLog, ex.toString()); + return null; + } + } + + /** + * Unescape all the string fields on the bean and all fields of objects referred to by the bean. + */ + private static <T> T doUnescapeDeep(T bean) + { + ReflectingStringUnescaperUnrestricted<T> escaper = + new ReflectingStringUnescaperUnrestricted<T>(true, bean); + return escaper.escape(); + } + +} + +/** + * @author Piotr Buczek + */ +class ReflectingStringUnescaperImpl<T> +{ + + protected final boolean isDeep; + + protected final T bean; + + protected ReflectingStringUnescaperImpl(boolean isDeep, T bean) + { + this.isDeep = isDeep; + this.bean = bean; + } + + protected T traverse(ReflectionStringTraverser.ReflectionFieldVisitor visitor) + { + if (isDeep) + { + ReflectionStringTraverser.traverseDeep(bean, visitor); + } else + { + ReflectionStringTraverser.traverseShallow(bean, visitor); + } + return bean; + } + +} + +/** + * Utility Class that preforms the unrestricted escaping. + * + * @author Piotr Buczek + */ +class ReflectingStringUnescaperUnrestricted<T> extends ReflectingStringUnescaperImpl<T> +{ + + private static class Visitor implements ReflectionStringTraverser.ReflectionFieldVisitor + { + public String tryVisit(String value, Object object, Field fieldOrNull) + { + if (null == fieldOrNull) + { + // happens e.g. when array of strings is unescaped + return StringEscapeUtils.unescapeHtml(value); + } + + return StringEscapeUtils.unescapeHtml(value); + } + } + + ReflectingStringUnescaperUnrestricted(boolean isDeep, T bean) + { + super(isDeep, bean); + } + + T escape() + { + return traverse(new Visitor()); + } +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringEscaperTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringEscaperTest.java index 51efad2c1db1237ad48b86ba20d08adfb3ab5dbe..210ee5ebe973a1aac6ccff80aa87078039753efd 100644 --- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringEscaperTest.java +++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringEscaperTest.java @@ -16,6 +16,8 @@ package ch.systemsx.cisd.common.utilities; +import java.io.Serializable; + import org.testng.AssertJUnit; import org.testng.annotations.Test; @@ -24,8 +26,10 @@ import org.testng.annotations.Test; */ public class ReflectingStringEscaperTest extends AssertJUnit { - public class TestBean + public static class TestBean implements Serializable { + private static final long serialVersionUID = 1L; + private String foo; private String bar; @@ -33,6 +37,11 @@ public class ReflectingStringEscaperTest extends AssertJUnit private String baz; private TestBean wrappedBean; + + public TestBean() + { + + } } @Test @@ -43,19 +52,19 @@ public class ReflectingStringEscaperTest extends AssertJUnit bean.bar = "<b>bar</b>"; bean.baz = "<i>baz</i>"; bean.wrappedBean = new TestBean(); - bean.wrappedBean.foo = "<a>foo</a>"; - bean.wrappedBean.bar = "<b>bar</b>"; - bean.wrappedBean.baz = "<i>baz</i>"; + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; TestBean escaped = ReflectingStringEscaper.escapeShallow(bean); - assertEquals(bean, escaped); + assertSame(bean, escaped); assertEquals("<a>foo</a>", bean.foo); assertEquals("<b>bar</b>", bean.bar); assertEquals("<i>baz</i>", bean.baz); - assertEquals("<a>foo</a>", bean.wrappedBean.foo); - assertEquals("<b>bar</b>", bean.wrappedBean.bar); - assertEquals("<i>baz</i>", bean.wrappedBean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); } @Test @@ -66,21 +75,51 @@ public class ReflectingStringEscaperTest extends AssertJUnit bean.bar = "<b>bar</b>"; bean.baz = "<i>baz</i>"; bean.wrappedBean = new TestBean(); - bean.wrappedBean.foo = "<a>foo</a>"; - bean.wrappedBean.bar = "<b>bar</b>"; - bean.wrappedBean.baz = "<i>baz</i>"; + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; TestBean escaped = ReflectingStringEscaper.escapeDeep(bean); - assertEquals(bean, escaped); + assertSame(bean, escaped); assertEquals("<a>foo</a>", bean.foo); assertEquals("<b>bar</b>", bean.bar); assertEquals("<i>baz</i>", bean.baz); - assertEquals("<a>foo</a>", bean.wrappedBean.foo); - assertEquals("<b>bar</b>", bean.wrappedBean.bar); - assertEquals("<i>baz</i>", bean.wrappedBean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); + } + + @Test + public void testDeepEscaperWithCopy() + { + TestBean bean = new TestBean(); + bean.foo = "<a>foo</a>"; + bean.bar = "<b>bar</b>"; + bean.baz = "<i>baz</i>"; + bean.wrappedBean = new TestBean(); + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; + + TestBean escaped = ReflectingStringEscaper.escapeDeepWithCopy(bean); + assertNotSame(bean, escaped); + assertEquals("<a>foo</a>", bean.foo); + assertEquals("<b>bar</b>", bean.bar); + assertEquals("<i>baz</i>", bean.baz); + assertEquals("<a>foo</a>", escaped.foo); + assertEquals("<b>bar</b>", escaped.bar); + assertEquals("<i>baz</i>", escaped.baz); + + assertNotSame(bean.wrappedBean, escaped.wrappedBean); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); + assertEquals("<wa>foo</wa>", escaped.wrappedBean.foo); + assertEquals("<wb>bar</wb>", escaped.wrappedBean.bar); + assertEquals("<wi>baz</wi>", escaped.wrappedBean.baz); } - + @Test public void testCircular() { @@ -89,20 +128,20 @@ public class ReflectingStringEscaperTest extends AssertJUnit bean.bar = "<b>bar</b>"; bean.baz = "<i>baz</i>"; bean.wrappedBean = new TestBean(); - bean.wrappedBean.foo = "<a>foo</a>"; - bean.wrappedBean.bar = "<b>bar</b>"; - bean.wrappedBean.baz = "<i>baz</i>"; + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; bean.wrappedBean.wrappedBean = bean; TestBean escaped = ReflectingStringEscaper.escapeDeep(bean); - assertEquals(bean, escaped); + assertSame(bean, escaped); assertEquals("<a>foo</a>", bean.foo); assertEquals("<b>bar</b>", bean.bar); assertEquals("<i>baz</i>", bean.baz); - assertEquals("<a>foo</a>", bean.wrappedBean.foo); - assertEquals("<b>bar</b>", bean.wrappedBean.bar); - assertEquals("<i>baz</i>", bean.wrappedBean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); } @Test @@ -113,19 +152,19 @@ public class ReflectingStringEscaperTest extends AssertJUnit bean.bar = "<b>bar</b>"; bean.baz = "<i>baz</i>"; bean.wrappedBean = new TestBean(); - bean.wrappedBean.foo = "<a>foo</a>"; - bean.wrappedBean.bar = "<b>bar</b>"; - bean.wrappedBean.baz = "<i>baz</i>"; + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; TestBean escaped = ReflectingStringEscaper.escapeShallowRestricted(bean, "foo", "baz"); - assertEquals(bean, escaped); + assertSame(bean, escaped); assertEquals("<a>foo</a>", bean.foo); assertEquals("<b>bar</b>", bean.bar); assertEquals("<i>baz</i>", bean.baz); - assertEquals("<a>foo</a>", bean.wrappedBean.foo); - assertEquals("<b>bar</b>", bean.wrappedBean.bar); - assertEquals("<i>baz</i>", bean.wrappedBean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); } @Test @@ -136,18 +175,18 @@ public class ReflectingStringEscaperTest extends AssertJUnit bean.bar = "<b>bar</b>"; bean.baz = "<i>baz</i>"; bean.wrappedBean = new TestBean(); - bean.wrappedBean.foo = "<a>foo</a>"; - bean.wrappedBean.bar = "<b>bar</b>"; - bean.wrappedBean.baz = "<i>baz</i>"; + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; TestBean escaped = ReflectingStringEscaper.escapeDeepRestricted(bean, "foo", "baz"); - assertEquals(bean, escaped); + assertSame(bean, escaped); assertEquals("<a>foo</a>", bean.foo); assertEquals("<b>bar</b>", bean.bar); assertEquals("<i>baz</i>", bean.baz); - assertEquals("<a>foo</a>", bean.wrappedBean.foo); - assertEquals("<b>bar</b>", bean.wrappedBean.bar); - assertEquals("<i>baz</i>", bean.wrappedBean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); } } diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringUnescaperTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringUnescaperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..456a7bc0fa5e42b4f58031dd3b1dbf38481874dd --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ReflectingStringUnescaperTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * Licensed 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 ch.systemsx.cisd.common.utilities; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +/** + * @author Piotr Buczek + */ +public class ReflectingStringUnescaperTest extends AssertJUnit +{ + public class TestBean + { + private String foo; + + private String bar; + + private String baz; + + private TestBean wrappedBean; + } + + @Test + public void testDeepEscaper() + { + TestBean bean = new TestBean(); + bean.foo = "<a>foo</a>"; + bean.bar = "<b>bar</b>"; + bean.baz = "<i>baz</i>"; + bean.wrappedBean = new TestBean(); + bean.wrappedBean.foo = "<wa>foo</wa>"; + bean.wrappedBean.bar = "<wb>bar</wb>"; + bean.wrappedBean.baz = "<wi>baz</wi>"; + + // first escape + TestBean escaped = ReflectingStringEscaper.escapeDeep(bean); + assertSame(bean, escaped); + assertEquals("<a>foo</a>", bean.foo); + assertEquals("<b>bar</b>", bean.bar); + assertEquals("<i>baz</i>", bean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); + + // unescaping goes back to original + TestBean unescaped = ReflectingStringUnescaper.unescapeDeep(bean); + assertSame(bean, unescaped); + assertEquals("<a>foo</a>", bean.foo); + assertEquals("<b>bar</b>", bean.bar); + assertEquals("<i>baz</i>", bean.baz); + assertEquals("<wa>foo</wa>", bean.wrappedBean.foo); + assertEquals("<wb>bar</wb>", bean.wrappedBean.bar); + assertEquals("<wi>baz</wi>", bean.wrappedBean.baz); + } + +}