Author: nbelaevski
Date: 2010-05-05 17:26:35 -0400 (Wed, 05 May 2010)
New Revision: 16899
Removed:
branches/community/3.3.X/framework/api/src/test/java/org/ajax4jsf/framework/util/javascript/
Modified:
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/Messages.java
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/javascript/ScriptUtils.java
branches/community/3.3.X/framework/api/src/test/java/org/ajax4jsf/javascript/ScriptUtilsTest.java
branches/community/3.3.X/framework/impl/src/main/resources/org/ajax4jsf/messages.properties
Log:
https://jira.jboss.org/jira/browse/RFPL-570
https://jira.jboss.org/jira/browse/RFPL-540
https://jira.jboss.org/jira/browse/RF-4903
Modified: branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/Messages.java
===================================================================
---
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/Messages.java 2010-05-05
19:47:22 UTC (rev 16898)
+++
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/Messages.java 2010-05-05
21:26:35 UTC (rev 16899)
@@ -365,7 +365,8 @@
public static final String DATASCROLLER_PAGE_MISSING =
"DATASCROLLER_PAGE_MISSING";
public static final String DATASCROLLER_PAGES_DIFFERENT =
"DATASCROLLER_PAGES_DIFFERENT";
public static final String COMPONENT_CONVERSION_ERROR =
"COMPONENT_CONVERSION_ERROR";
-
+ public static final String JAVASCRIPT_CIRCULAR_REFERENCE =
"JAVASCRIPT_CIRCULAR_REFERENCE";
+
public static void main(String[] args) {
String m = getMessage(INVALID_ATTRIBUTE_VALUE, "A", "B");
System.out.println(m);
Modified:
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/javascript/ScriptUtils.java
===================================================================
---
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/javascript/ScriptUtils.java 2010-05-05
19:47:22 UTC (rev 16898)
+++
branches/community/3.3.X/framework/api/src/main/java/org/ajax4jsf/javascript/ScriptUtils.java 2010-05-05
21:26:35 UTC (rev 16899)
@@ -26,32 +26,55 @@
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Array;
+import java.text.MessageFormat;
import java.util.Collection;
+import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.MissingResourceException;
import javax.faces.FacesException;
import javax.faces.context.ResponseWriter;
+import org.ajax4jsf.Messages;
import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
/**
* @author shura (latest modification by $Author: alexsmirnov $)
* @version $Revision: 1.1.2.3 $ $Date: 2007/01/24 13:22:31 $
- *
+ *
*/
public class ScriptUtils {
- /**
+ private static final Log LOG = LogFactory.getLog(ScriptUtils.class);
+
+ /**
* This is utility class, don't instantiate.
*/
private ScriptUtils() {
}
- private static void writeScriptToStream(Writer writer, Object obj) throws IOException {
- if (null == obj) {
+ private static void writeScriptToStream(Writer writer, Object obj, Map<Object,
Boolean> cycleBusterMap) throws IOException {
+ Boolean cycleBusterValue = cycleBusterMap.put(obj, Boolean.TRUE);
+
+ if (cycleBusterValue != null) {
+ if (LOG.isDebugEnabled()) {
+ String formattedMessage;
+ try {
+ formattedMessage = Messages.getMessage(Messages.JAVASCRIPT_CIRCULAR_REFERENCE,
obj);
+ } catch (MissingResourceException e) {
+ //ignore exception: workaround for unit tests
+ formattedMessage = MessageFormat.format("Circular reference serializing
object to JS: {0}", obj);
+ }
+
+ LOG.debug(formattedMessage);
+ }
writer.write("null");
+ } else if (null == obj) {
+ writer.write("null");
} else if (obj instanceof ScriptString) {
writer.write(((ScriptString) obj).toScript());
} else if (obj.getClass().isArray()) {
@@ -62,17 +85,17 @@
if (!first) {
writer.write(',');
}
- writeScriptToStream(writer, element);
+ writeScriptToStream(writer, element, cycleBusterMap);
first = false;
}
-
+
writer.write("] ");
- } else if (obj instanceof Collection) {
+ } else if (obj instanceof Collection<?>) {
// Collections put as JavaScript array.
-
+
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) obj;
-
+
writer.write("[");
boolean first = true;
for (Iterator<Object> iter = collection.iterator(); iter.hasNext();) {
@@ -80,12 +103,12 @@
if (!first) {
writer.write(',');
}
- writeScriptToStream(writer, element);
+ writeScriptToStream(writer, element, cycleBusterMap);
first = false;
}
writer.write("] ");
- } else if (obj instanceof Map) {
-
+ } else if (obj instanceof Map<?, ?>) {
+
// Maps put as JavaScript hash.
@SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>) obj;
@@ -96,10 +119,10 @@
if (!first) {
writer.write(',');
}
-
+
writeEncodedString(writer, entry.getKey());
writer.write(":");
- writeScriptToStream(writer, entry.getValue());
+ writeScriptToStream(writer, entry.getValue(), cycleBusterMap);
first = false;
}
writer.write("} ");
@@ -109,13 +132,13 @@
} else if (obj instanceof String) {
// all other put as encoded strings.
writeEncodedString(writer, obj);
- } else if (obj instanceof Enum) {
+ } else if (obj instanceof Enum<?>) {
// all other put as encoded strings.
writeEncodedString(writer, obj);
} else if (obj.getClass().getName().startsWith("java.sql.")) {
writer.write("{");
boolean first = true;
- for (PropertyDescriptor propertyDescriptor :
+ for (PropertyDescriptor propertyDescriptor :
PropertyUtils.getPropertyDescriptors(obj)) {
String key = propertyDescriptor.getName();
if ("class".equals(key)) {
@@ -131,11 +154,11 @@
if (!first) {
writer.write(',');
}
-
+
writeEncodedString(writer, key);
writer.write(":");
- writeScriptToStream(writer, value);
-
+ writeScriptToStream(writer, value, cycleBusterMap);
+
first = false;
}
writer.write("} ");
@@ -162,7 +185,7 @@
}
writeEncodedString(writer, key);
writer.write(":");
-
+
Object propertyValue;
try{
propertyValue = PropertyUtils.getProperty(obj, key);
@@ -170,27 +193,32 @@
throw new FacesException(
"Error in conversion Java Object to JavaScript", e);
}
-
- writeScriptToStream(writer, propertyValue);
+
+ writeScriptToStream(writer, propertyValue, cycleBusterMap);
first = false;
}
writer.write("} ");
}
+
+ if (cycleBusterValue == null) {
+ cycleBusterMap.remove(obj);
+ }
}
-
+
/**
- * Convert any Java Object to JavaScript representation ( as possible ) and write it to
+ * Convert any Java Object to JavaScript representation ( as possible ) and write it to
* writer immediately
- *
+ *
* @param responseWriter
* @param obj
* @throws IOException
*/
public static void writeToStream(final ResponseWriter responseWriter, Object obj) throws
IOException {
- writeScriptToStream(new ResponseWriterWrapper(responseWriter), obj);
+ Map<Object, Boolean> cycleBusterMap = new IdentityHashMap<Object,
Boolean>();
+ writeScriptToStream(new ResponseWriterWrapper(responseWriter), obj, cycleBusterMap);
}
-
+
/**
* Convert any Java Object to JavaScript representation ( as possible ).
* @param obj
@@ -199,7 +227,8 @@
public static String toScript(Object obj) {
StringBuilder sb = new StringBuilder();
try {
- writeScriptToStream(new StringBuilderWriter(sb), obj);
+ Map<Object, Boolean> cycleBusterMap = new IdentityHashMap<Object,
Boolean>();
+ writeScriptToStream(new StringBuilderWriter(sb), obj, cycleBusterMap);
} catch (IOException e) {
//ignore
}
@@ -211,7 +240,7 @@
writeEncoded(w, obj);
w.write("'");
}
-
+
public static void addEncodedString(StringBuilder buff, Object obj) {
try {
writeEncodedString(new StringBuilderWriter(buff), obj);
@@ -232,7 +261,7 @@
}
}
}
-
+
public static void addEncoded(StringBuilder buff, Object obj) {
try {
writeEncoded(new StringBuilderWriter(buff), obj);
@@ -242,11 +271,11 @@
}
public static String getValidJavascriptName(String s) {
-
+
StringBuffer buf = null;
for (int i = 0, len = s.length(); i < len; i++) {
char c = s.charAt(i);
-
+
if (Character.isLetterOrDigit(c)||c=='_' ) {
// allowed char
if (buf != null)
@@ -256,13 +285,13 @@
buf = new StringBuffer(s.length() + 10);
buf.append(s.substring(0, i));
}
-
+
buf.append('_');
if (c < 16) {
// pad single hex digit values with '0' on the left
buf.append('0');
}
-
+
if (c < 128) {
// first 128 chars match their byte representation in UTF-8
buf.append(Integer.toHexString(c).toUpperCase());
@@ -273,7 +302,7 @@
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
-
+
for (int j = 0; j < bytes.length; j++) {
int intVal = bytes[j];
if (intVal < 0) {
@@ -287,9 +316,9 @@
}
}
}
-
+
}
-
+
return buf == null ? s : buf.toString();
}
}
Modified:
branches/community/3.3.X/framework/api/src/test/java/org/ajax4jsf/javascript/ScriptUtilsTest.java
===================================================================
---
branches/community/3.3.X/framework/api/src/test/java/org/ajax4jsf/javascript/ScriptUtilsTest.java 2010-05-05
19:47:22 UTC (rev 16898)
+++
branches/community/3.3.X/framework/api/src/test/java/org/ajax4jsf/javascript/ScriptUtilsTest.java 2010-05-05
21:26:35 UTC (rev 16899)
@@ -22,14 +22,18 @@
package org.ajax4jsf.javascript;
import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isNull;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
-import static org.easymock.classextension.EasyMock.*;
+import static org.easymock.classextension.EasyMock.verify;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -54,11 +58,11 @@
*
*/
public static class Bean {
-
+
int _integer;
boolean _bool;
Object _foo;
-
+
public Bean() {
}
/**
@@ -109,6 +113,56 @@
}
}
+ public static class ReferencedBean {
+
+ private String name;
+
+ private ReferenceHolderBean parent;
+
+ public ReferencedBean(String name, ReferenceHolderBean parent) {
+ super();
+ this.name = name;
+ this.parent = parent;
+ }
+
+ public ReferenceHolderBean getParent() {
+ return parent;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public static class ReferenceHolderBean {
+
+ private String name;
+
+ private Object reference;
+
+ public ReferenceHolderBean(String name) {
+ super();
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getReference() {
+ return reference;
+ }
+
+ public void setReference(Object reference) {
+ this.reference = reference;
+ }
+
+ }
+
+ private static String dehydrate(String s) {
+ return s != null ? s.replaceAll("\\s", "") : s;
+ }
+
/**
* @param name
*/
@@ -139,7 +193,7 @@
java.sql.Date obj1 = new java.sql.Date(1);
assertNotNull(ScriptUtils.toScript(obj1));
}
-
+
/**
* Test method for {@link
org.ajax4jsf.javascript.ScriptUtils#toScript(java.lang.Object)}.
*/
@@ -200,7 +254,7 @@
List<Bean> obj = Arrays.asList(array);
assertEquals("[{'bool':true,'foo':'foo',\'integer\':1}
,{'bool':false,'foo':'bar','integer':2} ] ",
ScriptUtils.toScript(obj));
}
-
+
/**
* Test method for {@link
org.ajax4jsf.javascript.ScriptUtils#toScript(java.lang.Object)}.
*/
@@ -235,30 +289,30 @@
public void testNull() throws Exception {
assertEquals("null", ScriptUtils.toScript(null));
}
-
+
/**
* Test method for {@link ScriptUtils#toScript(Object)}
*/
public void testScriptString() throws Exception {
assertEquals("alert(x<y);", ScriptUtils.toScript(new
JSLiteral("alert(x<y);")));
}
-
+
private static enum TestEnum {
A, B, C;
-
+
@Override
public String toString() {
return "TestEnum: " + super.toString();
}
}
-
+
/**
* Test method for {@link ScriptUtils#toScript(Object)}
*/
public void testEnum() throws Exception {
assertEquals("'TestEnum: B'", ScriptUtils.toScript(TestEnum.B));
}
-
+
private void assertCaptureEquals(Capture<? extends Object> capture, String
expected) {
StringBuilder sb = new StringBuilder();
List<? extends Object> list = capture.getValues();
@@ -266,10 +320,10 @@
assertNotNull(o);
sb.append(o);
}
-
+
assertEquals(expected, sb.toString().trim());
}
-
+
/**
* Test method for {@link ScriptUtils#writeToStream(javax.faces.context.ResponseWriter,
Object)}
*/
@@ -277,7 +331,7 @@
ResponseWriter mockWriter = createNiceMock(ResponseWriter.class);
Capture<? extends Object> capture = new Capture<Object>(CaptureType.ALL) {
/**
- *
+ *
*/
private static final long serialVersionUID = -4915440411892856583L;
@@ -291,17 +345,70 @@
}
}
};
-
-
+
+
mockWriter.writeText(capture(capture), (String) isNull());
expectLastCall().anyTimes();
mockWriter.writeText((char[])capture(capture), eq(0), eq(1));
expectLastCall().anyTimes();
-
+
replay(mockWriter);
ScriptUtils.writeToStream(mockWriter, Collections.singletonMap("delay",
Integer.valueOf(1500)));
verify(mockWriter);
-
+
assertCaptureEquals(capture, "{'delay':1500}");
}
+
+ public void testCircularReferenceBeans() throws Exception {
+ ReferenceHolderBean parent = new ReferenceHolderBean("parent");
+ ReferencedBean child = new ReferencedBean("child", parent);
+
+ assertEquals(dehydrate("{'name': 'child', 'parent':
{'name': 'parent', 'reference': null}}"),
+ dehydrate(ScriptUtils.toScript(child)));
+ }
+
+ public void testCircularReferenceViaProperty() throws Exception {
+ ReferenceHolderBean parent = new ReferenceHolderBean("parent");
+ ReferencedBean child = new ReferencedBean("child", parent);
+
+ parent.setReference(child);
+
+ assertEquals(dehydrate("{'name': 'parent', 'reference':
{'name': 'child', 'parent': null}}"),
+ dehydrate(ScriptUtils.toScript(parent)));
+ }
+
+ public void testCircularReferenceViaArray() throws Exception {
+ ReferenceHolderBean parent = new ReferenceHolderBean("parent");
+ ReferencedBean child = new ReferencedBean("child", parent);
+
+ parent.setReference(new Object[] {child});
+
+ assertEquals(dehydrate("{'name': 'parent', 'reference':
[{'name': 'child', 'parent': null}]}"),
+ dehydrate(ScriptUtils.toScript(parent)));
+ }
+
+ public void testCircularReferenceViaCollection() throws Exception {
+ ReferenceHolderBean parent = new ReferenceHolderBean("parent");
+ ReferencedBean child = new ReferencedBean("child", parent);
+
+ Collection<Object> set = new ArrayList<Object>();
+ set.add(child);
+ parent.setReference(set);
+
+ assertEquals(dehydrate("{'name': 'parent', 'reference':
[{'name': 'child', 'parent': null}]}"),
+ dehydrate(ScriptUtils.toScript(parent)));
+ }
+
+ public void testCircularReferenceViaMap() throws Exception {
+ ReferenceHolderBean parent = new ReferenceHolderBean("parent");
+ ReferencedBean child = new ReferencedBean("child", parent);
+
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("key", child);
+ parent.setReference(map);
+
+ assertEquals(dehydrate("{'name': 'parent', 'reference':
{'key': {'name': 'child', 'parent': null}}}"),
+ dehydrate(ScriptUtils.toScript(parent)));
+ }
+
}
Modified:
branches/community/3.3.X/framework/impl/src/main/resources/org/ajax4jsf/messages.properties
===================================================================
---
branches/community/3.3.X/framework/impl/src/main/resources/org/ajax4jsf/messages.properties 2010-05-05
19:47:22 UTC (rev 16898)
+++
branches/community/3.3.X/framework/impl/src/main/resources/org/ajax4jsf/messages.properties 2010-05-05
21:26:35 UTC (rev 16899)
@@ -259,4 +259,5 @@
INVALID_VALUE="Component {0} has invalid value expression {1}"
DATASCROLLER_PAGE_MISSING=Datascroller {0}: The requested page #{1} isn''t found
in the model containing {2} pages. Paging is reset to page #{3}
DATASCROLLER_PAGES_DIFFERENT=Datascroller components attached to component\: {0} have
different values of ''page'' attribute \: {1}
-COMPONENT_CONVERSION_ERROR=Component {0}\: conversion error occurred for value {1}
\ No newline at end of file
+COMPONENT_CONVERSION_ERROR=Component {0}\: conversion error occurred for value {1}
+JAVASCRIPT_CIRCULAR_REFERENCE=Circular reference occurred during serialization of {0}
object to JavaScript. Such references are not currently supported and will be nullified.
\ No newline at end of file