classesVisited) {
+ boolean okayToContinue = true;
+
+ if (!classesVisited.contains(thisClass)) {
+ classesVisited.add(thisClass);
+ okayToContinue = visitor.visit(thisClass);
+
+ if (okayToContinue) {
+ Class[] interfaces = thisClass.getInterfaces();
+ int index = 0;
+ while ((index < interfaces.length) && (okayToContinue)) {
+ okayToContinue = visitUniqueInterfaces(interfaces[index++], visitor, classesVisited);
+ }
+
+ if (okayToContinue) {
+ Class superClass = thisClass.getSuperclass();
+ if ((superClass != null) && (!Object.class.equals(superClass))) {
+ okayToContinue = visitUniqueInterfaces(superClass, visitor, classesVisited);
+ }
+ }
+ }
+ }
+ return okayToContinue;
+ }
+
+ public static boolean isGzipInRequest(HttpServletRequest request) {
+ String header = request.getHeader("Accept-Encoding");
+ return (header != null) && (header.indexOf("gzip") >= 0);
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONWriter.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONWriter.java
new file mode 100644
index 000000000..c60367c3e
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONWriter.java
@@ -0,0 +1,549 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.text.CharacterIterator;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.text.StringCharacterIterator;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+import org.apache.struts2.json.annotations.JSON;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ *
+ * Serializes an object into JavaScript Object Notation (JSON). If cyclic
+ * references are detected they will be nulled out.
+ *
+ */
+class JSONWriter {
+ private static final Logger LOG = LoggerFactory.getLogger(JSONWriter.class);
+
+ /**
+ * By default, enums are serialzied as name=value pairs
+ */
+ public static final boolean ENUM_AS_BEAN_DEFAULT = false;
+
+ static char[] hex = "0123456789ABCDEF".toCharArray();
+ private StringBuilder buf = new StringBuilder();
+ private Stack stack = new Stack();
+ private boolean ignoreHierarchy = true;
+ private Object root;
+ private boolean buildExpr = true;
+ private String exprStack = "";
+ private Collection excludeProperties;
+ private Collection includeProperties;
+ private DateFormat formatter;
+ private boolean enumAsBean = ENUM_AS_BEAN_DEFAULT;
+ private boolean excludeNullProperties;
+
+ /**
+ * @param object
+ * Object to be serialized into JSON
+ * @return JSON string for object
+ * @throws JSONException
+ */
+ public String write(Object object) throws JSONException {
+ return this.write(object, null, null, false);
+ }
+
+ /**
+ * @param object
+ * Object to be serialized into JSON
+ * @return JSON string for object
+ * @throws JSONException
+ */
+ public String write(Object object, Collection excludeProperties,
+ Collection includeProperties, boolean excludeNullProperties) throws JSONException {
+ this.excludeNullProperties = excludeNullProperties;
+ this.buf.setLength(0);
+ this.root = object;
+ this.exprStack = "";
+ this.buildExpr = ((excludeProperties != null) && !excludeProperties.isEmpty())
+ || ((includeProperties != null) && !includeProperties.isEmpty());
+ this.excludeProperties = excludeProperties;
+ this.includeProperties = includeProperties;
+ this.value(object, null);
+
+ return this.buf.toString();
+ }
+
+ /**
+ * Detect cyclic references
+ */
+ private void value(Object object, Method method) throws JSONException {
+ if (object == null) {
+ this.add("null");
+
+ return;
+ }
+
+ if (this.stack.contains(object)) {
+ Class clazz = object.getClass();
+
+ // cyclic reference
+ if (clazz.isPrimitive() || clazz.equals(String.class)) {
+ this.process(object, method);
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Cyclic reference detected on " + object);
+ }
+
+ this.add("null");
+ }
+
+ return;
+ }
+
+ this.process(object, method);
+ }
+
+ /**
+ * Serialize object into json
+ */
+ private void process(Object object, Method method) throws JSONException {
+ this.stack.push(object);
+
+ if (object instanceof Class) {
+ this.string(object);
+ } else if (object instanceof Boolean) {
+ this.bool(((Boolean) object).booleanValue());
+ } else if (object instanceof Number) {
+ this.add(object);
+ } else if (object instanceof String) {
+ this.string(object);
+ } else if (object instanceof Character) {
+ this.string(object);
+ } else if (object instanceof Map) {
+ this.map((Map) object, method);
+ } else if (object.getClass().isArray()) {
+ this.array(object, method);
+ } else if (object instanceof Iterable) {
+ this.array(((Iterable) object).iterator(), method);
+ } else if (object instanceof Date) {
+ this.date((Date) object, method);
+ } else if (object instanceof Calendar) {
+ this.date(((Calendar) object).getTime(), method);
+ } else if (object instanceof Locale) {
+ this.string(object);
+ } else if (object instanceof Enum) {
+ this.enumeration((Enum) object);
+ } else {
+ this.bean(object);
+ }
+
+ this.stack.pop();
+ }
+
+ /**
+ * Instrospect bean and serialize its properties
+ */
+ private void bean(Object object) throws JSONException {
+ this.add("{");
+
+ BeanInfo info;
+
+ try {
+ Class clazz = object.getClass();
+
+ info = ((object == this.root) && this.ignoreHierarchy) ? Introspector.getBeanInfo(clazz, clazz
+ .getSuperclass()) : Introspector.getBeanInfo(clazz);
+
+ PropertyDescriptor[] props = info.getPropertyDescriptors();
+
+ boolean hasData = false;
+ for (int i = 0; i < props.length; ++i) {
+ PropertyDescriptor prop = props[i];
+ String name = prop.getName();
+ Method accessor = prop.getReadMethod();
+ Method baseAccessor = null;
+ if (clazz.getName().indexOf("$$EnhancerByCGLIB$$") > -1) {
+ try {
+ baseAccessor = Class.forName(
+ clazz.getName().substring(0, clazz.getName().indexOf("$$"))).getMethod(
+ accessor.getName(), accessor.getParameterTypes());
+ } catch (Exception ex) {
+ LOG.debug(ex.getMessage(), ex);
+ }
+ } else
+ baseAccessor = accessor;
+
+ if (baseAccessor != null) {
+
+ JSON json = baseAccessor.getAnnotation(JSON.class);
+ if (json != null) {
+ if (!json.serialize())
+ continue;
+ else if (json.name().length() > 0)
+ name = json.name();
+ }
+
+ // ignore "class" and others
+ if (this.shouldExcludeProperty(clazz, prop)) {
+ continue;
+ }
+ String expr = null;
+ if (this.buildExpr) {
+ expr = this.expandExpr(name);
+ if (this.shouldExcludeProperty(expr)) {
+ continue;
+ }
+ expr = this.setExprStack(expr);
+ }
+
+ Object value = accessor.invoke(object, new Object[0]);
+ boolean propertyPrinted = this.add(name, value, accessor, hasData);
+ hasData = hasData || propertyPrinted;
+ if (this.buildExpr) {
+ this.setExprStack(expr);
+ }
+ }
+ }
+
+ // special-case handling for an Enumeration - include the name() as
+ // a property */
+ if (object instanceof Enum) {
+ Object value = ((Enum) object).name();
+ this.add("_name", value, object.getClass().getMethod("name"), hasData);
+ }
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+
+ this.add("}");
+ }
+
+ /**
+ * Instrospect an Enum and serialize it as a name/value pair or as a bean
+ * including all its own properties
+ */
+ private void enumeration(Enum enumeration) throws JSONException {
+ if (enumAsBean) {
+ this.bean(enumeration);
+ } else {
+ this.string(enumeration.name());
+ }
+ }
+
+ /**
+ * Ignore "class" field
+ */
+ private boolean shouldExcludeProperty(Class clazz, PropertyDescriptor prop) throws SecurityException,
+ NoSuchFieldException {
+ String name = prop.getName();
+
+ if (name.equals("class") || name.equals("declaringClass") || name.equals("cachedSuperClass")
+ || name.equals("metaClass")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private String expandExpr(int i) {
+ return this.exprStack + "[" + i + "]";
+ }
+
+ private String expandExpr(String property) {
+ if (this.exprStack.length() == 0)
+ return property;
+ return this.exprStack + "." + property;
+ }
+
+ private String setExprStack(String expr) {
+ String s = this.exprStack;
+ this.exprStack = expr;
+ return s;
+ }
+
+ private boolean shouldExcludeProperty(String expr) {
+ if (this.excludeProperties != null) {
+ for (Pattern pattern : this.excludeProperties) {
+ if (pattern.matcher(expr).matches()) {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring property because of exclude rule: " + expr);
+ return true;
+ }
+ }
+ }
+
+ if (this.includeProperties != null) {
+ for (Pattern pattern : this.includeProperties) {
+ if (pattern.matcher(expr).matches()) {
+ return false;
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring property because of include rule: " + expr);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add name/value pair to buffer
+ */
+ private boolean add(String name, Object value, Method method, boolean hasData) throws JSONException {
+ if (!excludeNullProperties || (value != null)) {
+ if (hasData) {
+ this.add(',');
+ }
+ this.add('"');
+ this.add(name);
+ this.add("\":");
+ this.value(value, method);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add map to buffer
+ */
+ private void map(Map map, Method method) throws JSONException {
+ this.add("{");
+
+ Iterator it = map.entrySet().iterator();
+
+ boolean warnedNonString = false; // one report per map
+ boolean hasData = false;
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Object key = entry.getKey();
+ String expr = null;
+ if (this.buildExpr) {
+ if (key == null) {
+ LOG.error("Cannot build expression for null key in " + this.exprStack);
+ continue;
+ } else {
+ expr = this.expandExpr(key.toString());
+ if (this.shouldExcludeProperty(expr)) {
+ continue;
+ }
+ expr = this.setExprStack(expr);
+ }
+ }
+ if (hasData) {
+ this.add(',');
+ }
+ hasData = true;
+ if (!warnedNonString && !(key instanceof String)) {
+ LOG.warn("JavaScript doesn't support non-String keys, using toString() on "
+ + key.getClass().getName());
+ warnedNonString = true;
+ }
+ this.value(key.toString(), method);
+ this.add(":");
+ this.value(entry.getValue(), method);
+ if (this.buildExpr) {
+ this.setExprStack(expr);
+ }
+ }
+
+ this.add("}");
+ }
+
+ /**
+ * Add date to buffer
+ */
+ private void date(Date date, Method method) {
+ JSON json = null;
+ if (method != null)
+ json = method.getAnnotation(JSON.class);
+ if (this.formatter == null)
+ this.formatter = new SimpleDateFormat(JSONUtil.RFC3339_FORMAT);
+
+ DateFormat formatter = (json != null) && (json.format().length() > 0) ? new SimpleDateFormat(json
+ .format()) : this.formatter;
+ this.string(formatter.format(date));
+ }
+
+ /**
+ * Add array to buffer
+ */
+ private void array(Iterator it, Method method) throws JSONException {
+ this.add("[");
+
+ boolean hasData = false;
+ for (int i = 0; it.hasNext(); i++) {
+ String expr = null;
+ if (this.buildExpr) {
+ expr = this.expandExpr(i);
+ if (this.shouldExcludeProperty(expr)) {
+ it.next();
+ continue;
+ }
+ expr = this.setExprStack(expr);
+ }
+ if (hasData) {
+ this.add(',');
+ }
+ hasData = true;
+ this.value(it.next(), method);
+ if (this.buildExpr) {
+ this.setExprStack(expr);
+ }
+ }
+
+ this.add("]");
+ }
+
+ /**
+ * Add array to buffer
+ */
+ private void array(Object object, Method method) throws JSONException {
+ this.add("[");
+
+ int length = Array.getLength(object);
+
+ boolean hasData = false;
+ for (int i = 0; i < length; ++i) {
+ String expr = null;
+ if (this.buildExpr) {
+ expr = this.expandExpr(i);
+ if (this.shouldExcludeProperty(expr)) {
+ continue;
+ }
+ expr = this.setExprStack(expr);
+ }
+ if (hasData) {
+ this.add(',');
+ }
+ hasData = true;
+ this.value(Array.get(object, i), method);
+ if (this.buildExpr) {
+ this.setExprStack(expr);
+ }
+ }
+
+ this.add("]");
+ }
+
+ /**
+ * Add boolean to buffer
+ */
+ private void bool(boolean b) {
+ this.add(b ? "true" : "false");
+ }
+
+ /**
+ * escape characters
+ */
+ private void string(Object obj) {
+ this.add('"');
+
+ CharacterIterator it = new StringCharacterIterator(obj.toString());
+
+ for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
+ if (c == '"') {
+ this.add("\\\"");
+ } else if (c == '\\') {
+ this.add("\\\\");
+ } else if (c == '/') {
+ this.add("\\/");
+ } else if (c == '\b') {
+ this.add("\\b");
+ } else if (c == '\f') {
+ this.add("\\f");
+ } else if (c == '\n') {
+ this.add("\\n");
+ } else if (c == '\r') {
+ this.add("\\r");
+ } else if (c == '\t') {
+ this.add("\\t");
+ } else if (Character.isISOControl(c)) {
+ this.unicode(c);
+ } else {
+ this.add(c);
+ }
+ }
+
+ this.add('"');
+ }
+
+ /**
+ * Add object to buffer
+ */
+ private void add(Object obj) {
+ this.buf.append(obj);
+ }
+
+ /**
+ * Add char to buffer
+ */
+ private void add(char c) {
+ this.buf.append(c);
+ }
+
+ /**
+ * Represent as unicode
+ *
+ * @param c
+ * character to be encoded
+ */
+ private void unicode(char c) {
+ this.add("\\u");
+
+ int n = c;
+
+ for (int i = 0; i < 4; ++i) {
+ int digit = (n & 0xf000) >> 12;
+
+ this.add(hex[digit]);
+ n <<= 4;
+ }
+ }
+
+ public void setIgnoreHierarchy(boolean ignoreHierarchy) {
+ this.ignoreHierarchy = ignoreHierarchy;
+ }
+
+ /**
+ * If true, an Enum is serialized as a bean with a special property
+ * _name=name() as all as all other properties defined within the enum.
+ * If false, an Enum is serialized as a name=value pair (name=name())
+ *
+ * @param enumAsBean
+ * true to serialize an enum as a bean instead of as a name=value
+ * pair (default=false)
+ */
+ public void setEnumAsBean(boolean enumAsBean) {
+ this.enumAsBean = enumAsBean;
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/SerializationParams.java b/plugins/json/src/main/java/org/apache/struts2/json/SerializationParams.java
new file mode 100644
index 000000000..efc6c5a48
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/SerializationParams.java
@@ -0,0 +1,120 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.xwork.StringUtils;
+
+public class SerializationParams {
+ private static final String DEFAULT_CONTENT_TYPE = "application/json";
+
+ private final HttpServletResponse response;
+ private final String encoding;
+ private final boolean wrapWithComments;
+ private final String serializedJSON;
+ private final boolean smd;
+ private final boolean gzip;
+ private final boolean noCache;
+ private final int statusCode;
+ private final int errorCode;
+ private final boolean prefix;
+ private String contentType = DEFAULT_CONTENT_TYPE;
+ private String wrapPrefix;
+ private String wrapSuffix;
+
+ public SerializationParams(HttpServletResponse response, String encoding, boolean wrapWithComments,
+ String serializedJSON, boolean smd, boolean gzip, boolean noCache, int statusCode, int errorCode,
+ boolean prefix, String contentType, String wrapPrefix, String wrapSuffix) {
+ this.response = response;
+ this.encoding = encoding;
+ this.wrapWithComments = wrapWithComments;
+ this.serializedJSON = serializedJSON;
+ this.smd = smd;
+ this.gzip = gzip;
+ this.noCache = noCache;
+ this.statusCode = statusCode;
+ this.errorCode = errorCode;
+ this.prefix = prefix;
+ this.contentType = StringUtils.defaultString(contentType, DEFAULT_CONTENT_TYPE);
+ this.wrapPrefix = wrapPrefix;
+ this.wrapSuffix = wrapSuffix;
+ }
+
+ public SerializationParams(HttpServletResponse response, String defaultEncoding,
+ boolean wrapWithComments, String json, boolean b, boolean b1, boolean noCache, int i, int i1,
+ boolean prefix, String contentType) {
+ this(response, defaultEncoding, wrapWithComments, json, b, b1, noCache, i, i1, prefix, contentType,
+ null, null);
+ }
+
+ public String getWrapSuffix() {
+ return wrapSuffix;
+ }
+
+ public String getWrapPrefix() {
+ return wrapPrefix;
+ }
+
+ public HttpServletResponse getResponse() {
+ return response;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public boolean isWrapWithComments() {
+ return wrapWithComments;
+ }
+
+ public String getSerializedJSON() {
+ return serializedJSON;
+ }
+
+ public boolean isSmd() {
+ return smd;
+ }
+
+ public boolean isGzip() {
+ return gzip;
+ }
+
+ public boolean isNoCache() {
+ return noCache;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ public int getErrorCode() {
+ return errorCode;
+ }
+
+ public boolean isPrefix() {
+ return prefix;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/annotations/JSON.java b/plugins/json/src/main/java/org/apache/struts2/json/annotations/JSON.java
new file mode 100644
index 000000000..584ea72cf
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/annotations/JSON.java
@@ -0,0 +1,41 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/*
+ * Annotation used to customize serialization
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JSON {
+ String name() default "";
+
+ boolean serialize() default true;
+
+ boolean deserialize() default true;
+
+ String format() default "";
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMD.java b/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMD.java
new file mode 100644
index 000000000..a3bd2797c
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMD.java
@@ -0,0 +1,36 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SMD {
+ String version() default org.apache.struts2.json.smd.SMD.DEFAULT_VERSION;
+
+ String objectName();
+
+ String serviceType() default org.apache.struts2.json.smd.SMD.DEFAULT_SERVICE_TYPE;
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java b/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java
new file mode 100644
index 000000000..a910135fa
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMDMethod.java
@@ -0,0 +1,32 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SMDMethod {
+ String name() default "";
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java b/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java
new file mode 100644
index 000000000..2d28d5007
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/annotations/SMDMethodParameter.java
@@ -0,0 +1,32 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SMDMethodParameter {
+ String name();
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCError.java b/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCError.java
new file mode 100644
index 000000000..c9a66ceb6
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCError.java
@@ -0,0 +1,109 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.rpc;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/*
+ * Used to serialize RPC Errors
+ */
+public class RPCError {
+ private static final Logger LOG = LoggerFactory.getLogger(RPCError.class);
+
+ private int code;
+ private String name;
+ private String message;
+ private String stack;
+
+ public RPCError() {
+ }
+
+ public RPCError(String message, int code) {
+ this.code = code;
+ this.message = message;
+
+ LOG.error(message);
+ }
+
+ public RPCError(String message, RPCErrorCode code) {
+ this(message, code.code());
+ }
+
+ public RPCError(Throwable t, int code, boolean debug) {
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+
+ this.code = code;
+ this.message = t.getMessage();
+ this.name = t.getClass().getName();
+
+ if (debug) {
+ StringWriter s = new StringWriter();
+ PrintWriter w = new PrintWriter(s);
+ t.printStackTrace(w);
+ w.flush();
+ this.stack = s.toString();
+ }
+
+ LOG.error(t.getMessage(), t);
+ }
+
+ public RPCError(Throwable t, RPCErrorCode code, boolean debug) {
+ this(t, code.code(), debug);
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getStack() {
+ return stack;
+ }
+
+ public void setStack(String stack) {
+ this.stack = stack;
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java b/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java
new file mode 100644
index 000000000..bef720d86
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCErrorCode.java
@@ -0,0 +1,47 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.rpc;
+
+public enum RPCErrorCode {
+ MISSING_METHOD(100, "'method' parameter is missing in request"),
+ MISSING_ID(100, "'id' parameter is missing in request"),
+ INVALID_PROCEDURE_CALL(0, "Invalid procedure call"),
+ METHOD_NOT_FOUND(101, "Procedure not found"),
+ PARAMETERS_MISMATCH(102, "Parameters count in request does not patch parameters count on method"),
+ EXCEPTION(103, "An exception was thrown"),
+ SMD_DISABLED(104, "SMD is disabled");
+
+ private int code;
+ private String message;
+
+ RPCErrorCode(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int code() {
+ return code;
+ }
+
+ public String message() {
+ return this.message;
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java b/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java
new file mode 100644
index 000000000..24b2537e1
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/rpc/RPCResponse.java
@@ -0,0 +1,54 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.rpc;
+
+/**
+ * Class that will be serialized as a response to an RPC call
+ */
+public class RPCResponse {
+ private String id;
+ private Object result;
+ private RPCError error;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Object getResult() {
+ return result;
+ }
+
+ public void setResult(Object result) {
+ this.result = result;
+ }
+
+ public RPCError getError() {
+ return error;
+ }
+
+ public void setError(RPCError error) {
+ this.error = error;
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/smd/SMD.java b/plugins/json/src/main/java/org/apache/struts2/json/smd/SMD.java
new file mode 100644
index 000000000..c5ddf05c3
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/smd/SMD.java
@@ -0,0 +1,78 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.smd;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/*
+ * HOlds SMD declarations for a class
+ */
+public class SMD {
+ public static final String DEFAULT_VERSION = ".1";
+ public static final String DEFAULT_SERVICE_TYPE = "JSON-RPC";
+
+ private String version = DEFAULT_VERSION;
+ private String objectName;
+ private String serviceType = DEFAULT_SERVICE_TYPE;
+ private String serviceUrl;
+ private Set methods = new TreeSet();
+
+ public void addSMDMethod(SMDMethod method) {
+ this.methods.add(method);
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getObjectName() {
+ return this.objectName;
+ }
+
+ public void setObjectName(String objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getServiceType() {
+ return this.serviceType;
+ }
+
+ public void setServiceType(String serviceType) {
+ this.serviceType = serviceType;
+ }
+
+ public String getServiceUrl() {
+ return this.serviceUrl;
+ }
+
+ public void setServiceUrl(String serviceUrl) {
+ this.serviceUrl = serviceUrl;
+ }
+
+ public Set getMethods() {
+ return this.methods;
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDMethod.java b/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDMethod.java
new file mode 100644
index 000000000..abd42b327
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDMethod.java
@@ -0,0 +1,75 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.smd;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class SMDMethod implements Comparable {
+ private String name;
+ private Set parameters = new TreeSet();
+
+ public SMDMethod(String name) {
+ this.name = name;
+ }
+
+ public void addSMDMethodParameter(SMDMethodParameter parameter) {
+ this.parameters.add(parameter);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getParameters() {
+ return this.parameters;
+ }
+
+ public int compareTo(Object o) {
+ if (!(o instanceof SMDMethod))
+ return 1;
+ if (o == null)
+ return 1;
+ SMDMethod other = (SMDMethod) o;
+ if ((name == null) && (other.name == null))
+ return 0;
+ if (name == null)
+ return -1;
+ if (name.equals(other.name))
+ return parameters.size() - other.parameters.size();
+
+ return name.compareTo(other.name);
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SMDMethod))
+ return false;
+ SMDMethod toCompare = (SMDMethod) obj;
+ if ((name == null) && (toCompare.name == null))
+ return true;
+ return (name != null) && name.equals(toCompare.name)
+ && (parameters.size() == toCompare.parameters.size());
+ }
+}
diff --git a/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java b/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java
new file mode 100644
index 000000000..95797f21d
--- /dev/null
+++ b/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDMethodParameter.java
@@ -0,0 +1,57 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json.smd;
+
+public class SMDMethodParameter implements Comparable {
+ private String name;
+
+ public SMDMethodParameter(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int compareTo(Object o) {
+ if (!(o instanceof SMDMethodParameter))
+ return 1;
+ if (o == null)
+ return 1;
+ if ((name == null) && (((SMDMethodParameter) o).name == null))
+ return 0;
+ if (name == null)
+ return -1;
+ return name.compareTo(((SMDMethodParameter) o).name);
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof SMDMethodParameter))
+ return false;
+ if ((name == null) && (((SMDMethodParameter) o).name == null))
+ return true;
+ return (name != null) && name.equals(((SMDMethodParameter) o).name);
+ }
+}
diff --git a/plugins/json/src/main/resources/JsonPlugin.tld b/plugins/json/src/main/resources/JsonPlugin.tld
new file mode 100644
index 000000000..8c2c10dc4
--- /dev/null
+++ b/plugins/json/src/main/resources/JsonPlugin.tld
@@ -0,0 +1,25 @@
+
+
+
+
+ This exposes JSON util functions from the Struts JSON plugin
+
+
+ 1.0
+
+ json
+
+ /struts-json-tags
+
+
+ serialize
+ org.apache.struts2.json.JSONUtil
+
+ java.lang.String serialize(java.lang.Object)
+
+
+
+
diff --git a/plugins/json/src/main/resources/struts-plugin.xml b/plugins/json/src/main/resources/struts-plugin.xml
new file mode 100644
index 000000000..447987177
--- /dev/null
+++ b/plugins/json/src/main/resources/struts-plugin.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/AnEnum.java b/plugins/json/src/test/java/org/apache/struts2/json/AnEnum.java
new file mode 100644
index 000000000..94226d979
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/AnEnum.java
@@ -0,0 +1,28 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+/**
+ * An enumeration for JSON serialization testing
+ */
+public enum AnEnum {
+ ValueA, ValueB, ValueC
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/AnEnumBean.java b/plugins/json/src/test/java/org/apache/struts2/json/AnEnumBean.java
new file mode 100644
index 000000000..0f6313e62
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/AnEnumBean.java
@@ -0,0 +1,45 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+/**
+ * A more complicated Enum with additional properties.
+ */
+public enum AnEnumBean {
+
+ One("A", "B"), Two("C", "D"), Three("E", "F");
+
+ private String propA;
+ private String propB;
+
+ AnEnumBean(String propA, String propB) {
+ this.propA = propA;
+ this.propB = propB;
+ }
+
+ public String getPropA() {
+ return propA;
+ }
+
+ public String getPropB() {
+ return propB;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/Bean.java b/plugins/json/src/test/java/org/apache/struts2/json/Bean.java
new file mode 100644
index 000000000..8fb725a1c
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/Bean.java
@@ -0,0 +1,151 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public class Bean {
+ private String stringField;
+ private int intField;
+ private boolean booleanField;
+ private char charField;
+ private long longField;
+ private float floatField;
+ private double doubleField;
+ private Object objectField;
+ private byte byteField;
+ private AnEnum enumField;
+ private AnEnumBean enumBean;
+ private BigDecimal bigDecimal;
+ private BigInteger bigInteger;
+
+ /**
+ * @return the byteField
+ */
+ public byte getByteField() {
+ return this.byteField;
+ }
+
+ /**
+ * @param byteField
+ * the byteField to set
+ */
+ public void setByteField(byte byteField) {
+ this.byteField = byteField;
+ }
+
+ public boolean isBooleanField() {
+ return this.booleanField;
+ }
+
+ public void setBooleanField(boolean booleanField) {
+ this.booleanField = booleanField;
+ }
+
+ public char getCharField() {
+ return this.charField;
+ }
+
+ public void setCharField(char charField) {
+ this.charField = charField;
+ }
+
+ public double getDoubleField() {
+ return this.doubleField;
+ }
+
+ public void setDoubleField(double doubleField) {
+ this.doubleField = doubleField;
+ }
+
+ public float getFloatField() {
+ return this.floatField;
+ }
+
+ public void setFloatField(float floatField) {
+ this.floatField = floatField;
+ }
+
+ public int getIntField() {
+ return this.intField;
+ }
+
+ public void setIntField(int intField) {
+ this.intField = intField;
+ }
+
+ public long getLongField() {
+ return this.longField;
+ }
+
+ public void setLongField(long longField) {
+ this.longField = longField;
+ }
+
+ public Object getObjectField() {
+ return this.objectField;
+ }
+
+ public void setObjectField(Object objectField) {
+ this.objectField = objectField;
+ }
+
+ public String getStringField() {
+ return this.stringField;
+ }
+
+ public void setStringField(String stringField) {
+ this.stringField = stringField;
+ }
+
+ public AnEnum getEnumField() {
+ return enumField;
+ }
+
+ public void setEnumField(AnEnum enumField) {
+ this.enumField = enumField;
+ }
+
+ public AnEnumBean getEnumBean() {
+ return enumBean;
+ }
+
+ public void setEnumBean(AnEnumBean enumBean) {
+ this.enumBean = enumBean;
+ }
+
+ public BigInteger getBigInteger() {
+ return bigInteger;
+ }
+
+ public void setBigInteger(BigInteger bigInteger) {
+ this.bigInteger = bigInteger;
+ }
+
+ public BigDecimal getBigDecimal() {
+ return bigDecimal;
+ }
+
+ public void setBigDecimal(BigDecimal bigDecimal) {
+ this.bigDecimal = bigDecimal;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONEnumTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONEnumTest.java
new file mode 100644
index 000000000..b8b095436
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONEnumTest.java
@@ -0,0 +1,127 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Test serialization of an Enum in the two supported modes: - Enum as a
+ * name=value pair; - Enum as a bean
+ */
+public class JSONEnumTest extends TestCase {
+
+ /**
+ * Asserts that a bean can be serialized to JSON and restored as a map
+ */
+ public void testEnumAsNameValue() throws Exception {
+ Bean bean1 = new Bean();
+
+ bean1.setStringField("str");
+ bean1.setBooleanField(true);
+ bean1.setCharField('s');
+ bean1.setDoubleField(10.1);
+ bean1.setFloatField(1.5f);
+ bean1.setIntField(10);
+ bean1.setLongField(100);
+ bean1.setEnumField(AnEnum.ValueA);
+ bean1.setEnumBean(AnEnumBean.Two);
+
+ JSONWriter jsonWriter = new JSONWriter();
+ jsonWriter.setEnumAsBean(false);
+ String json = jsonWriter.write(bean1);
+
+ Map result = (Map) JSONUtil.deserialize(json);
+ assertEquals("str", result.get("stringField"));
+ assertEquals(true, result.get("booleanField"));
+ assertEquals("s", result.get("charField")); // note: this is a
+ // String
+ assertEquals(10.1, result.get("doubleField"));
+ assertEquals(1.5, result.get("floatField")); // note: this is a
+ // Double
+ assertEquals(10L, result.get("intField")); // note: this is a
+ // Long
+ assertEquals(AnEnum.ValueA, AnEnum.valueOf((String) result.get("enumField"))); // note:
+ // this
+ // is a
+ // String
+ assertEquals(AnEnumBean.Two, AnEnumBean.valueOf((String) result.get("enumBean"))); // note:
+ // this
+ // is a
+ // String
+ }
+
+ /**
+ * Asserts that a bean can be serialized to JSON and restored as a map
+ * In this case, the name of the enum is in _name and the two properties of
+ * AnEnumBean are also serialized
+ */
+ public void testEnumAsBean() throws Exception {
+ Bean bean1 = new Bean();
+
+ bean1.setStringField("str");
+ bean1.setBooleanField(true);
+ bean1.setCharField('s');
+ bean1.setDoubleField(10.1);
+ bean1.setFloatField(1.5f);
+ bean1.setIntField(10);
+ bean1.setLongField(100);
+ bean1.setEnumField(AnEnum.ValueA);
+ bean1.setEnumBean(AnEnumBean.Two);
+
+ JSONWriter jsonWriter = new JSONWriter();
+ jsonWriter.setEnumAsBean(true);
+ String json = jsonWriter.write(bean1);
+
+ Map result = (Map) JSONUtil.deserialize(json);
+ assertEquals("str", result.get("stringField"));
+ assertEquals(true, result.get("booleanField"));
+ assertEquals("s", result.get("charField")); // note: this is a
+ // String
+ assertEquals(10.1, result.get("doubleField"));
+ assertEquals(1.5, result.get("floatField")); // note: this is a
+ // Double
+ assertEquals(10L, result.get("intField")); // note: this is a
+ // Long
+ Map enumBean1 = (Map) result.get("enumField");
+ assertNotNull(enumBean1);
+ assertEquals(AnEnum.ValueA, AnEnum.valueOf((String) enumBean1.get("_name"))); // get
+ // the
+ // special
+ // name
+ // property
+ Map enumBean2 = (Map) result.get("enumBean");
+ assertEquals(AnEnumBean.Two, AnEnumBean.valueOf((String) enumBean2.get("_name"))); // get
+ // the
+ // special
+ // name
+ // property
+ assertEquals(AnEnumBean.Two.getPropA(), (String) enumBean2.get("propA")); // get
+ // the
+ // propA
+ // property
+ assertEquals(AnEnumBean.Two.getPropB(), (String) enumBean2.get("propB")); // get
+ // the
+ // propA
+ // property
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
new file mode 100644
index 000000000..86557ba53
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
@@ -0,0 +1,502 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.StrutsTestCase;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+import com.opensymphony.xwork2.util.ValueStack;
+
+public class JSONInterceptorTest extends StrutsTestCase {
+ private MockActionInvocationEx invocation;
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+
+ private void setRequestContent(String fileName) throws Exception {
+ String content = TestUtils.readContent(JSONInterceptorTest.class.getResource(fileName));
+ this.request.setContent(content.getBytes());
+ }
+
+ public void testBadJSON1() throws Exception {
+ tryBadJSON("bad-1.txt");
+ }
+
+ public void testBadJSON2() throws Exception {
+ tryBadJSON("bad-2.txt");
+ }
+
+ public void testBadJSON3() throws Exception {
+ tryBadJSON("bad-3.txt");
+ }
+
+ public void testBadJSON4() throws Exception {
+ tryBadJSON("bad-4.txt");
+ }
+
+ public void testBadJSON5() throws Exception {
+ tryBadJSON("bad-5.txt");
+ }
+
+ public void testBadToTheBoneJSON4() throws Exception {
+ tryBadJSON("bad-to-the-bone.txt");
+ }
+
+ private void tryBadJSON(String fileName) throws Exception {
+ // request
+ setRequestContent(fileName);
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+
+ // JSON is not well formed, throw exception
+ try {
+ interceptor.intercept(this.invocation);
+ fail("Should have thrown an exception");
+ } catch (JSONException e) {
+ // I can't get JUnit to ignore the exception
+ // @Test(expected = JSONException.class)
+ }
+ }
+
+ public void testSMDDisabledSMD() throws Exception {
+ // request
+ setRequestContent("smd-3.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+
+ // SMD was not enabled so invocation must happen
+ try {
+ interceptor.intercept(this.invocation);
+ } catch (JSONException e) {
+ fail("Should have not thrown an exception");
+ }
+
+ }
+
+ public void testSMDAliasedMethodCall1() throws Exception {
+ // request
+ setRequestContent("smd-14.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest2 action = new SMDActionTest2();
+
+ this.invocation.setAction(action);
+
+ interceptor.intercept(this.invocation);
+ // method was aliased, but was invoked with the regular name
+ // so method must not be invoked
+ assertFalse(this.invocation.isInvoked());
+ assertFalse(action.isDoSomethingInvoked());
+ }
+
+ public void testSMDAliasedMethodCall2() throws Exception {
+ // request
+ setRequestContent("smd-15.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest2 action = new SMDActionTest2();
+
+ this.invocation.setAction(action);
+
+ interceptor.intercept(this.invocation);
+ // method was aliased, but was invoked with the aliased name
+ // so method must be invoked
+ assertFalse(this.invocation.isInvoked());
+ assertTrue(action.isDoSomethingInvoked());
+ }
+
+ public void testSMDNoMethod() throws Exception {
+ // request
+ setRequestContent("smd-4.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+
+ // SMD was enabled so invocation must happen
+
+ interceptor.intercept(this.invocation);
+
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-13.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+
+ assertFalse(this.invocation.isInvoked());
+ }
+
+ public void testSMDMethodWithoutAnnotations() throws Exception {
+ // request
+ setRequestContent("smd-9.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+
+ // SMD was enabled so invocation must happen
+ try {
+ interceptor.intercept(this.invocation);
+ assertTrue("Exception was expected here!", true);
+ } catch (Exception e) {
+ // ok
+ }
+ assertFalse(this.invocation.isInvoked());
+ }
+
+ public void testSMDPrimitivesNoResult() throws Exception {
+ // request
+ setRequestContent("smd-6.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+
+ // can't be invoked
+ interceptor.intercept(this.invocation);
+ assertFalse(this.invocation.isInvoked());
+
+ // asert values were passed properly
+ assertEquals("string", action.getStringParam());
+ assertEquals(1, action.getIntParam());
+ assertEquals(true, action.isBooleanParam());
+ assertEquals('c', action.getCharParam());
+ assertEquals(2, action.getLongParam());
+ assertEquals(new Float(3.3), action.getFloatParam());
+ assertEquals(4.4, action.getDoubleParam());
+ assertEquals(5, action.getShortParam());
+ assertEquals(6, action.getByteParam());
+
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-11.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+
+ assertEquals("application/json-rpc;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testSMDReturnObject() throws Exception {
+ // request
+ setRequestContent("smd-10.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest2 action = new SMDActionTest2();
+
+ this.invocation.setAction(action);
+
+ // can't be invoked
+ interceptor.intercept(this.invocation);
+ assertFalse(this.invocation.isInvoked());
+
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-12.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+
+ assertEquals("application/json-rpc;charset=ISO-8859-1", response.getContentType());
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testSMDObjectsNoResult() throws Exception {
+ // request
+ setRequestContent("smd-7.txt");
+ this.request.addHeader("content-type", "application/json-rpc");
+
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setEnableSMD(true);
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+
+ // can't be invoked
+ interceptor.intercept(this.invocation);
+ assertFalse(this.invocation.isInvoked());
+
+ // asert values were passed properly
+ Bean bean = action.getBeanParam();
+ assertNotNull(bean);
+ assertTrue(bean.isBooleanField());
+ assertEquals(bean.getStringField(), "test");
+ assertEquals(bean.getIntField(), 10);
+ assertEquals(bean.getCharField(), 's');
+ assertEquals(bean.getDoubleField(), 10.1);
+ assertEquals(bean.getByteField(), 3);
+
+ List list = action.getListParam();
+ assertNotNull(list);
+ assertEquals("str0", list.get(0));
+ assertEquals("str1", list.get(1));
+
+ Map map = action.getMapParam();
+ assertNotNull(map);
+ assertNotNull(map.get("a"));
+ assertEquals(new Long(1), map.get("a"));
+ assertNotNull(map.get("c"));
+ List insideList = (List) map.get("c");
+ assertEquals(1.0d, insideList.get(0));
+ assertEquals(2.0d, insideList.get(1));
+
+ String json = response.getContentAsString();
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-11.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+
+ assertEquals("application/json-rpc;charset=ISO-8859-1", response.getContentType());
+ }
+
+ @SuppressWarnings( { "unchecked", "unchecked" })
+ public void testReadEmpty() throws Exception {
+ // request
+ setRequestContent("json-6.txt");
+ this.request.addHeader("content-type", "application/json");
+
+ // interceptor
+ JSONInterceptor interceptor = new JSONInterceptor();
+ TestAction action = new TestAction();
+
+ this.invocation.setAction(action);
+
+ interceptor.intercept(this.invocation);
+ }
+
+ @SuppressWarnings( { "unchecked", "unchecked" })
+ public void test() throws Exception {
+ // request
+ setRequestContent("json-1.txt");
+ this.request.addHeader("content-type", "application/json");
+
+ // interceptor
+ JSONInterceptor interceptor = new JSONInterceptor();
+ TestAction action = new TestAction();
+
+ this.invocation.setAction(action);
+
+ interceptor.intercept(this.invocation);
+
+ // serialize and compare
+ List list = action.getList();
+
+ assertNotNull(list);
+ assertEquals(list.size(), 10);
+
+ list = action.getCollection();
+ assertNotNull(list);
+ assertEquals(list.size(), 3);
+ assertEquals(list.get(0), "b");
+ assertEquals(list.get(1), 1L);
+ list = (List) list.get(2);
+ assertNotNull(list);
+ assertEquals(list.size(), 2);
+ assertEquals(list.get(0), 10L);
+ assertEquals(list.get(1), 12L);
+
+ list = action.getCollection2();
+ assertNotNull(list);
+ assertEquals(list.size(), 1);
+
+ // inside a map any primitive is either: String, Long, Boolean or Double
+ Map bean = (Map) list.get(0);
+
+ assertNotNull(bean);
+ assertTrue((Boolean) bean.get("booleanField"));
+ assertEquals(bean.get("charField"), "s");
+ assertEquals(bean.get("doubleField"), 10.1);
+ assertEquals(bean.get("floatField"), 1.5);
+ assertEquals(bean.get("intField"), 10L);
+ assertEquals(bean.get("longField"), 100L);
+ assertEquals(bean.get("stringField"), "str");
+
+ bean = (Map) bean.get("objectField");
+ assertNotNull(bean);
+ assertFalse((Boolean) bean.get("booleanField"));
+ assertEquals(bean.get("charField"), "\u0000");
+ assertEquals(bean.get("doubleField"), 2.2);
+ assertEquals(bean.get("floatField"), 1.1);
+ assertEquals(bean.get("intField"), 0L);
+ assertEquals(bean.get("longField"), 0L);
+ assertEquals(bean.get("stringField"), " ");
+
+ assertEquals(action.getFoo(), "foo");
+
+ Map map = action.getMap();
+
+ assertNotNull(map);
+ assertEquals(map.size(), 2);
+ assertEquals(map.get("a"), 1L);
+ list = (List) map.get("c");
+ assertNotNull(list);
+ assertEquals(list.size(), 2);
+ assertEquals(list.get(0), 1.0);
+ assertEquals(list.get(1), 2.0);
+
+ assertEquals(action.getResult(), null);
+
+ Bean bean2 = action.getBean();
+
+ assertNotNull(bean2);
+ assertTrue(bean2.isBooleanField());
+ assertEquals(bean2.getStringField(), "test");
+ assertEquals(bean2.getIntField(), 10);
+ assertEquals(bean2.getCharField(), 's');
+ assertEquals(bean2.getDoubleField(), 10.1);
+ assertEquals(bean2.getByteField(), 3);
+
+ String[] strArray = action.getArray();
+
+ assertNotNull(strArray);
+ assertEquals(strArray.length, 2);
+ assertEquals(strArray[0], "str0");
+ assertEquals(strArray[1], "str1");
+
+ int[] intArray = action.getIntArray();
+
+ assertNotNull(intArray);
+ assertEquals(intArray.length, 2);
+ assertEquals(intArray[0], 1);
+ assertEquals(intArray[1], 2);
+
+ Bean[] beanArray = action.getBeanArray();
+
+ assertNotNull(beanArray);
+ assertNotNull(beanArray[0]);
+ assertEquals(beanArray[0].getStringField(), "bean1");
+ assertNotNull(beanArray[1]);
+ assertEquals(beanArray[1].getStringField(), "bean2");
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(action.getDate());
+
+ assertEquals(calendar.get(Calendar.YEAR), 1999);
+ assertEquals(calendar.get(Calendar.MONTH), Calendar.DECEMBER);
+ assertEquals(calendar.get(Calendar.DAY_OF_MONTH), 31);
+ assertEquals(calendar.get(Calendar.HOUR), 11);
+ assertEquals(calendar.get(Calendar.MINUTE), 59);
+ assertEquals(calendar.get(Calendar.SECOND), 59);
+
+ calendar.setTime(action.getDate2());
+ assertEquals(calendar.get(Calendar.YEAR), 1999);
+ assertEquals(calendar.get(Calendar.MONTH), Calendar.DECEMBER);
+ assertEquals(calendar.get(Calendar.DAY_OF_MONTH), 31);
+
+ // test desrialize=false
+ assertNull(action.getFoo2());
+ }
+
+ public void testRoot() throws Exception {
+ setRequestContent("json-5.txt");
+ this.request.addHeader("content-type", "application/json");
+
+ // interceptor
+ JSONInterceptor interceptor = new JSONInterceptor();
+ interceptor.setRoot("bean");
+ TestAction4 action = new TestAction4();
+
+ this.invocation.setAction(action);
+ this.invocation.getStack().push(action);
+
+ interceptor.intercept(this.invocation);
+
+ Bean bean2 = action.getBean();
+
+ assertNotNull(bean2);
+ assertTrue(bean2.isBooleanField());
+ assertEquals(bean2.getStringField(), "test");
+ assertEquals(bean2.getIntField(), 10);
+ assertEquals(bean2.getCharField(), 's');
+ assertEquals(bean2.getDoubleField(), 10.1);
+ assertEquals(bean2.getByteField(), 3);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ this.request = new MockHttpServletRequest();
+ this.response = new MockHttpServletResponse();
+
+ ActionContext context = ActionContext.getContext();
+ ValueStack stack = context.getValueStack();
+
+ ActionContext.setContext(context);
+ context.put(StrutsStatics.HTTP_REQUEST, this.request);
+ context.put(StrutsStatics.HTTP_RESPONSE, this.response);
+
+ MockServletContext servletContext = new MockServletContext();
+
+ context.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
+ this.invocation = new MockActionInvocationEx();
+ this.invocation.setInvocationContext(context);
+ this.invocation.setStack(stack);
+ }
+}
+
+class MockActionInvocationEx extends MockActionInvocation {
+ private boolean invoked;
+
+ @Override
+ public String invoke() throws Exception {
+ this.invoked = true;
+ return super.invoke();
+ }
+
+ public boolean isInvoked() {
+ return this.invoked;
+ }
+
+ public void setInvoked(boolean invoked) {
+ this.invoked = invoked;
+ }
+
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONPopulatorTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONPopulatorTest.java
new file mode 100644
index 000000000..0fdf04073
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONPopulatorTest.java
@@ -0,0 +1,163 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.beans.IntrospectionException;
+import java.io.StringReader;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class JSONPopulatorTest extends TestCase {
+
+ public void testNulls() throws IntrospectionException, InvocationTargetException, NoSuchMethodException,
+ JSONException, InstantiationException, IllegalAccessException {
+ JSONPopulator populator = new JSONPopulator();
+ OtherBean bean = new OtherBean();
+ Map jsonMap = new HashMap();
+
+ jsonMap.put("intField", null);
+ jsonMap.put("booleanField", null);
+ jsonMap.put("charField", null);
+ jsonMap.put("longField", null);
+ jsonMap.put("floatField", null);
+ jsonMap.put("doubleField", null);
+ jsonMap.put("byteField", null);
+
+ populator.populateObject(bean, jsonMap);
+ assertNull(bean.getIntField());
+ assertNull(bean.isBooleanField());
+ assertNull(bean.getCharField());
+ assertNull(bean.getLongField());
+ assertNull(bean.getDoubleField());
+ assertNull(bean.getByteField());
+ }
+
+ public void testPrimitiveBean() throws Exception {
+ StringReader stringReader = new StringReader(TestUtils.readContent(JSONInterceptorTest.class
+ .getResource("json-7.txt")));
+ Object json = JSONUtil.deserialize(stringReader);
+ assertNotNull(json);
+ assertTrue(json instanceof Map);
+ Map jsonMap = (Map) json;
+ JSONPopulator populator = new JSONPopulator();
+ Bean bean = new Bean();
+ populator.populateObject(bean, jsonMap);
+ assertTrue(bean.isBooleanField());
+ assertEquals("test\u000E\u000f", bean.getStringField());
+ assertEquals(10, bean.getIntField());
+ assertEquals('s', bean.getCharField());
+ assertEquals(10.1d, bean.getDoubleField(), 0d);
+ assertEquals(3, bean.getByteField());
+ assertEquals(new BigDecimal(111111.5d), bean.getBigDecimal());
+ assertEquals(new BigInteger("111111"), bean.getBigInteger());
+ }
+
+ public void testObjectBean() throws Exception {
+ String text = TestUtils.readContent(JSONInterceptorTest.class.getResource("json-7.txt"));
+ Object json = JSONUtil.deserialize(text);
+ assertNotNull(json);
+ assertTrue(json instanceof Map);
+ Map jsonMap = (Map) json;
+ JSONPopulator populator = new JSONPopulator();
+ WrapperClassBean bean = new WrapperClassBean();
+ populator.populateObject(bean, jsonMap);
+ assertEquals(Boolean.TRUE, bean.getBooleanField());
+ assertEquals(true, bean.isPrimitiveBooleanField1());
+ assertEquals(false, bean.isPrimitiveBooleanField2());
+ assertEquals(false, bean.isPrimitiveBooleanField3());
+ assertEquals("test\u000E\u000f", bean.getStringField());
+ assertEquals(new Integer(10), bean.getIntField());
+ assertEquals(0, bean.getNullIntField());
+ assertEquals(new Character('s'), bean.getCharField());
+ assertEquals(10.1d, bean.getDoubleField());
+ assertEquals(new Byte((byte) 3), bean.getByteField());
+
+ assertEquals(2, bean.getListField().size());
+ assertEquals("1", bean.getListField().get(0).getValue());
+ assertEquals("2", bean.getListField().get(1).getValue());
+
+ assertEquals(1, bean.getListMapField().size());
+ assertEquals(2, bean.getListMapField().get(0).size());
+ assertEquals(new Long(2073501), bean.getListMapField().get(0).get("id1"));
+ assertEquals(new Long(3), bean.getListMapField().get(0).get("id2"));
+
+ assertEquals(2, bean.getMapListField().size());
+ assertEquals(3, bean.getMapListField().get("id1").size());
+ assertEquals(new Long(2), bean.getMapListField().get("id1").get(1));
+ assertEquals(4, bean.getMapListField().get("id2").size());
+ assertEquals(new Long(3), bean.getMapListField().get("id2").get(1));
+
+ assertEquals(1, bean.getArrayMapField().length);
+ assertEquals(2, bean.getArrayMapField()[0].size());
+ assertEquals(new Long(2073501), bean.getArrayMapField()[0].get("id1"));
+ assertEquals(new Long(3), bean.getArrayMapField()[0].get("id2"));
+ }
+
+ public void testObjectBeanWithStrings() throws Exception {
+ StringReader stringReader = new StringReader(TestUtils.readContent(JSONInterceptorTest.class
+ .getResource("json-8.txt")));
+ Object json = JSONUtil.deserialize(stringReader);
+ assertNotNull(json);
+ assertTrue(json instanceof Map);
+ Map jsonMap = (Map) json;
+ JSONPopulator populator = new JSONPopulator();
+ WrapperClassBean bean = new WrapperClassBean();
+ populator.populateObject(bean, jsonMap);
+ assertEquals(Boolean.TRUE, bean.getBooleanField());
+ assertEquals("test", bean.getStringField());
+ assertEquals(new Integer(10), bean.getIntField());
+ assertEquals(new Character('s'), bean.getCharField());
+ assertEquals(10.1d, bean.getDoubleField());
+ assertEquals(new Byte((byte) 3), bean.getByteField());
+
+ assertEquals(null, bean.getListField());
+ assertEquals(null, bean.getListMapField());
+ assertEquals(null, bean.getMapListField());
+ assertEquals(null, bean.getArrayMapField());
+ }
+
+ public void testInfiniteLoop() throws JSONException {
+ try {
+ JSONReader reader = new JSONReader();
+ reader.read("[1,\"a]");
+ fail("Should have thrown an exception");
+ } catch (JSONException e) {
+ // I can't get JUnit to ignore the exception
+ // @Test(expected = JSONException.class)
+ }
+ }
+
+ public void testParseBadInput() throws JSONException {
+ try {
+ JSONReader reader = new JSONReader();
+ reader.read("[1,\"a\"1]");
+ fail("Should have thrown an exception");
+ } catch (JSONException e) {
+ // I can't get JUnit to ignore the exception
+ // @Test(expected = JSONException.class)
+ }
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONResultTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONResultTest.java
new file mode 100644
index 000000000..942aa9a44
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONResultTest.java
@@ -0,0 +1,534 @@
+/*
+ + * $Id$
+ + *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.struts2.StrutsStatics;
+import org.apache.struts2.StrutsTestCase;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ * JSONResultTest
+ */
+public class JSONResultTest extends StrutsTestCase {
+ MockActionInvocation invocation;
+ MockHttpServletResponse response;
+ MockServletContext servletContext;
+ ActionContext context;
+ ValueStack stack;
+ MockHttpServletRequest request;
+
+ public void testJSONUtilNPEOnNullMehtod() {
+ Map map = new HashMap();
+ map.put("createtime", new Date());
+ try {
+ JSONUtil.serialize(map);
+ } catch (JSONException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testJSONWriterEndlessLoopOnExludedProperties() throws JSONException {
+ Pattern all = Pattern.compile(".*");
+
+ JSONWriter writer = new JSONWriter();
+ writer.write(Arrays.asList("a", "b"), Arrays.asList(all), null, false);
+ }
+
+ public void testSMDDisabledSMD() throws Exception {
+ JSONResult result = new JSONResult();
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String smd = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(smd, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-8.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ public void testSMDDefault() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setEnableSMD(true);
+ SMDActionTest1 action = new SMDActionTest1();
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String smd = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(smd, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-1.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testSMDDefaultAnnotations() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setEnableSMD(true);
+ SMDActionTest2 action = new SMDActionTest2();
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String smd = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(smd, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("smd-2.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testExcludeNullPropeties() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setExcludeNullProperties(true);
+ TestAction action = new TestAction();
+ action.setFoo("fool");
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String smd = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(smd, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("nulls-1.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ public void testWrapPrefix() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setWrapPrefix("_prefix_");
+ TestAction2 action = new TestAction2();
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String out = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(out, true);
+ String normalizedExpected = "_prefix_{\"name\":\"name\"}";
+ assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ public void testSuffix() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setWrapSuffix("_suffix_");
+ TestAction2 action = new TestAction2();
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String out = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(out, true);
+ String normalizedExpected = "{\"name\":\"name\"}_suffix_";
+ assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ public void testPrefixAndSuffix() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setWrapPrefix("_prefix_");
+ result.setWrapSuffix("_suffix_");
+ TestAction2 action = new TestAction2();
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String out = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(out, true);
+ String normalizedExpected = "_prefix_{\"name\":\"name\"}_suffix_";
+ assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ public void testPrefix() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setExcludeNullProperties(true);
+ result.setPrefix(true);
+ TestAction action = new TestAction();
+ action.setFoo("fool");
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String smd = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(smd, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("prefix-1.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void test() throws Exception {
+ JSONResult result = new JSONResult();
+
+ TestAction action = new TestAction();
+
+ // test scape characters
+ action.setArray(new String[] { "a", "a", "\"", "\\", "/", "\b", "\f", "\n", "\r", "\t" });
+
+ List list = new ArrayList();
+
+ list.add("b");
+ list.add(1);
+ list.add(new int[] { 10, 12 });
+ action.setCollection(list);
+
+ // beans
+ List collection2 = new ArrayList();
+ Bean bean1 = new Bean();
+
+ bean1.setBigDecimal(new BigDecimal("111111.111111"));
+ bean1.setBigInteger(new BigInteger("111111111111"));
+ bean1.setStringField("str");
+ bean1.setBooleanField(true);
+ bean1.setCharField('s');
+ bean1.setDoubleField(10.1);
+ bean1.setFloatField(1.5f);
+ bean1.setIntField(10);
+ bean1.setLongField(100);
+ bean1.setEnumField(AnEnum.ValueA);
+ bean1.setEnumBean(AnEnumBean.One);
+
+ Bean bean2 = new Bean();
+
+ bean2.setStringField(" ");
+ bean2.setBooleanField(false);
+ bean2.setFloatField(1.1f);
+ bean2.setDoubleField(2.2);
+ bean2.setEnumField(AnEnum.ValueB);
+ bean2.setEnumBean(AnEnumBean.Two);
+
+ // circular reference
+ bean1.setObjectField(bean2);
+ bean2.setObjectField(bean1);
+
+ collection2.add(bean1);
+ action.setCollection2(collection2);
+
+ // keep order in map
+ Map map = new LinkedHashMap();
+
+ map.put("a", 1);
+ map.put("c", new float[] { 1.0f, 2.0f });
+ action.setMap(map);
+
+ action.setFoo("foo");
+ // should be ignored, marked 'transient'
+ action.setBar("bar");
+
+ // date
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.YEAR, 1999);
+ calendar.set(Calendar.MONTH, Calendar.DECEMBER);
+ calendar.set(Calendar.DAY_OF_MONTH, 31);
+ calendar.set(Calendar.HOUR_OF_DAY, 11);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ action.setDate(calendar.getTime());
+ action.setDate2(calendar.getTime());
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testHierarchy() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setIgnoreHierarchy(false);
+
+ TestAction3 action = new TestAction3();
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String json = response.getContentAsString();
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json-4.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testCommentWrap() throws Exception {
+ JSONResult result = new JSONResult();
+
+ TestAction action = new TestAction();
+
+ // test scape characters
+ action.setArray(new String[] { "a", "a", "\"", "\\", "/", "\b", "\f", "\n", "\r", "\t" });
+
+ List list = new ArrayList();
+
+ list.add("b");
+ list.add(1);
+ list.add(new int[] { 10, 12 });
+ action.setCollection(list);
+
+ // beans
+ List collection2 = new ArrayList();
+ Bean bean1 = new Bean();
+
+ bean1.setStringField("str");
+ bean1.setBooleanField(true);
+ bean1.setCharField('s');
+ bean1.setDoubleField(10.1);
+ bean1.setFloatField(1.5f);
+ bean1.setIntField(10);
+ bean1.setLongField(100);
+ bean1.setEnumField(null);
+ bean1.setEnumBean(null);
+
+ Bean bean2 = new Bean();
+
+ bean2.setStringField(" ");
+ bean2.setBooleanField(false);
+ bean2.setFloatField(1.1f);
+ bean2.setDoubleField(2.2);
+ bean2.setEnumField(AnEnum.ValueC);
+ bean2.setEnumBean(AnEnumBean.Three);
+
+ // circular reference
+ bean1.setObjectField(bean2);
+ bean2.setObjectField(bean1);
+
+ collection2.add(bean1);
+ action.setCollection2(collection2);
+
+ // keep order in map
+ Map map = new LinkedHashMap();
+
+ map.put("a", 1);
+ map.put("c", new float[] { 1.0f, 2.0f });
+ action.setMap(map);
+
+ action.setFoo("foo");
+ // should be ignored, marked 'transient'
+ action.setBar("bar");
+
+ // date
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.YEAR, 1999);
+ calendar.set(Calendar.MONTH, Calendar.DECEMBER);
+ calendar.set(Calendar.DAY_OF_MONTH, 31);
+ calendar.set(Calendar.HOUR_OF_DAY, 11);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ action.setDate(calendar.getTime());
+ action.setDate2(calendar.getTime());
+
+ this.invocation.setAction(action);
+ result.setWrapWithComments(true);
+ result.execute(this.invocation);
+
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json-3.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ private void executeTest2Action(JSONResult result) throws Exception {
+ TestAction action = new TestAction();
+
+ // beans
+ Bean bean1 = new Bean();
+
+ bean1.setStringField("str");
+ bean1.setBooleanField(true);
+ bean1.setCharField('s');
+ bean1.setDoubleField(10.1);
+ bean1.setFloatField(1.5f);
+ bean1.setIntField(10);
+ bean1.setLongField(100);
+ bean1.setEnumField(AnEnum.ValueA);
+ bean1.setEnumBean(AnEnumBean.One);
+
+ // set root
+ action.setBean(bean1);
+ result.setRoot("bean");
+
+ stack.push(action);
+ this.invocation.setStack(stack);
+ this.invocation.setAction(action);
+
+ result.execute(this.invocation);
+ }
+
+ public void test2() throws Exception {
+ JSONResult result = new JSONResult();
+
+ executeTest2Action(result);
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json-2.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testJSONP() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setCallbackParameter("callback");
+ request.addParameter("callback", "exec");
+
+ executeTest2Action(result);
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("jsonp-1.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testNoCache() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setNoCache(true);
+
+ executeTest2Action(result);
+
+ assertEquals("no-cache", response.getHeader("Cache-Control"));
+ assertEquals("0", response.getHeader("Expires"));
+ assertEquals("No-cache", response.getHeader("Pragma"));
+ }
+
+ public void testContentType() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setContentType("some_super_content");
+
+ executeTest2Action(result);
+
+ assertEquals("some_super_content;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testStatusCode() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setStatusCode(HttpServletResponse.SC_CONTINUE);
+
+ executeTest2Action(result);
+
+ assertEquals(HttpServletResponse.SC_CONTINUE, response.getStatus());
+ }
+
+ /**
+ * Repeats test2 but with the Enum serialized as a bean
+ */
+ public void test2WithEnumBean() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setEnumAsBean(true);
+
+ executeTest2Action(result);
+
+ String json = response.getContentAsString();
+
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json-2-enum.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ /**
+ * Ensures that properties of given root object are read as shallow
+ * (non-recursive) unless specifically included.
+ */
+ public void testIncludeProperties() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setIncludeProperties("foo");
+ TestAction action = new TestAction();
+ action.setFoo("fooValue");
+ action.setBean(new Bean());
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String json = response.getContentAsString();
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json-9.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ public void testIncludePropertiesWithList() throws Exception {
+ JSONResult result = new JSONResult();
+ result.setIncludeProperties("^list\\[\\d+\\]\\.booleanField");
+ TestAction action = new TestAction();
+
+ List list = new ArrayList();
+
+ list.add(new Bean());
+ list.add(new Bean());
+ list.add(new Bean());
+
+ action.setList(list);
+
+ this.invocation.setAction(action);
+ result.execute(this.invocation);
+
+ String json = response.getContentAsString();
+ String normalizedActual = TestUtils.normalize(json, true);
+ String normalizedExpected = TestUtils.normalize(JSONResultTest.class.getResource("json-10.txt"));
+ assertEquals(normalizedExpected, normalizedActual);
+ assertEquals("application/json;charset=ISO-8859-1", response.getContentType());
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.response = new MockHttpServletResponse();
+ this.request = new MockHttpServletRequest();
+ this.request.setRequestURI("http://sumeruri");
+ this.context = ActionContext.getContext();
+ this.context.put(StrutsStatics.HTTP_RESPONSE, this.response);
+ this.context.put(StrutsStatics.HTTP_REQUEST, this.request);
+ this.stack = context.getValueStack();
+ this.servletContext = new MockServletContext();
+ this.context.put(StrutsStatics.SERVLET_CONTEXT, this.servletContext);
+ this.invocation = new MockActionInvocation();
+ this.invocation.setInvocationContext(this.context);
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONUtilTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONUtilTest.java
new file mode 100644
index 000000000..3f1e3c306
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONUtilTest.java
@@ -0,0 +1,66 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class JSONUtilTest extends TestCase {
+
+ /**
+ * Asserts that a bean can be serialized to JSON and restored as a map
+ */
+ public void testSerializeDeserialize() throws Exception {
+ Bean bean1 = new Bean();
+
+ bean1.setStringField("str");
+ bean1.setBooleanField(true);
+ bean1.setCharField('s');
+ bean1.setDoubleField(10.1);
+ bean1.setFloatField(1.5f);
+ bean1.setIntField(10);
+ bean1.setLongField(100);
+ bean1.setEnumField(AnEnum.ValueA);
+ bean1.setEnumBean(AnEnumBean.Two);
+
+ String json = JSONUtil.serialize(bean1);
+
+ Map result = (Map) JSONUtil.deserialize(json);
+ assertEquals("str", result.get("stringField"));
+ assertEquals(true, result.get("booleanField"));
+ assertEquals("s", result.get("charField")); // note: this is a
+ // String
+ assertEquals(10.1, result.get("doubleField"));
+ assertEquals(1.5, result.get("floatField")); // note: this is a
+ // Double
+ assertEquals(10L, result.get("intField")); // note: this is a
+ // Long
+ assertEquals(AnEnum.ValueA, AnEnum.valueOf((String) result.get("enumField"))); // note:
+ // this
+ // is a
+ // String
+ assertEquals(AnEnumBean.Two, AnEnumBean.valueOf((String) result.get("enumBean"))); // note:
+ // this
+ // is a
+ // String
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/OtherBean.java b/plugins/json/src/test/java/org/apache/struts2/json/OtherBean.java
new file mode 100644
index 000000000..fa6f62933
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/OtherBean.java
@@ -0,0 +1,97 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+public class OtherBean {
+ private int primitiveInt;
+
+ private Integer intField;
+ private Boolean booleanField;
+ private Character charField;
+ private Long longField;
+ private Float floatField;
+ private Double doubleField;
+ private Byte byteField;
+
+ public Boolean isBooleanField() {
+ return booleanField;
+ }
+
+ public void setBooleanField(Boolean booleanField) {
+ this.booleanField = booleanField;
+ }
+
+ public Byte getByteField() {
+ return byteField;
+ }
+
+ public void setByteField(Byte byteField) {
+ this.byteField = byteField;
+ }
+
+ public Character getCharField() {
+ return charField;
+ }
+
+ public void setCharField(Character charField) {
+ this.charField = charField;
+ }
+
+ public Double getDoubleField() {
+ return doubleField;
+ }
+
+ public void setDoubleField(Double doubleField) {
+ this.doubleField = doubleField;
+ }
+
+ public Float getFloatField() {
+ return floatField;
+ }
+
+ public void setFloatField(Float floatField) {
+ this.floatField = floatField;
+ }
+
+ public Integer getIntField() {
+ return intField;
+ }
+
+ public void setIntField(Integer intField) {
+ this.intField = intField;
+ }
+
+ public Long getLongField() {
+ return longField;
+ }
+
+ public void setLongField(Long longField) {
+ this.longField = longField;
+ }
+
+ public int getPrimitiveInt() {
+ return primitiveInt;
+ }
+
+ public void setPrimitiveInt(int primitiveInt) {
+ this.primitiveInt = primitiveInt;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/SMDActionTest1.java b/plugins/json/src/test/java/org/apache/struts2/json/SMDActionTest1.java
new file mode 100644
index 000000000..245ccf2f2
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/SMDActionTest1.java
@@ -0,0 +1,197 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.struts2.json.annotations.SMDMethod;
+
+public class SMDActionTest1 {
+ private boolean addWasCalled;
+ @SuppressWarnings("unchecked")
+ private List listParam;
+ @SuppressWarnings("unchecked")
+ private Map mapParam;
+ private Bean beanParam;
+ private String stringParam;
+ private int intParam;
+ private boolean booleanParam;
+ private char charParam;
+ private long longParam;
+ private float floatParam;
+ private double doubleParam;
+ private short shortParam;
+ private Object objectParam;
+ private byte byteParam;
+
+ @SMDMethod
+ public void doSomethingPrimitives(String stringParam, int intParam, boolean booleanParam, char charParam,
+ long longParam, float floatParam, double doubleParam, short shortParam, byte byteParam) {
+ this.stringParam = stringParam;
+ this.intParam = intParam;
+ this.booleanParam = booleanParam;
+ this.charParam = charParam;
+ this.longParam = longParam;
+ this.floatParam = floatParam;
+ this.doubleParam = doubleParam;
+ this.byteParam = byteParam;
+ this.shortParam = shortParam;
+ }
+
+ @SuppressWarnings("unchecked")
+ @SMDMethod
+ public void doSomethingObjects(Bean beanParam, Map mapParam, List listParam) {
+ this.beanParam = beanParam;
+ this.mapParam = mapParam;
+ this.listParam = listParam;
+ }
+
+ @SMDMethod
+ public void add(int a, int b) {
+ this.addWasCalled = true;
+ }
+
+ @SMDMethod
+ public void doSomething() {
+
+ }
+
+ public void methodWithoutAnnotation() {
+
+ }
+
+ public boolean isAddWasCalled() {
+ return this.addWasCalled;
+ }
+
+ public void setAddWasCalled(boolean addWasCalled) {
+ this.addWasCalled = addWasCalled;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getListParam() {
+ return this.listParam;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setListParam(List listParam) {
+ this.listParam = listParam;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map getMapParam() {
+ return this.mapParam;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setMapParam(Map mapParam) {
+ this.mapParam = mapParam;
+ }
+
+ public Bean getBeanParam() {
+ return this.beanParam;
+ }
+
+ public void setBeanParam(Bean beanParam) {
+ this.beanParam = beanParam;
+ }
+
+ public String getStringParam() {
+ return this.stringParam;
+ }
+
+ public void setStringParam(String stringParam) {
+ this.stringParam = stringParam;
+ }
+
+ public int getIntParam() {
+ return this.intParam;
+ }
+
+ public void setIntParam(int intParam) {
+ this.intParam = intParam;
+ }
+
+ public boolean isBooleanParam() {
+ return this.booleanParam;
+ }
+
+ public void setBooleanParam(boolean booleanParam) {
+ this.booleanParam = booleanParam;
+ }
+
+ public char getCharParam() {
+ return this.charParam;
+ }
+
+ public void setCharParam(char charParam) {
+ this.charParam = charParam;
+ }
+
+ public long getLongParam() {
+ return this.longParam;
+ }
+
+ public void setLongParam(long longParam) {
+ this.longParam = longParam;
+ }
+
+ public float getFloatParam() {
+ return this.floatParam;
+ }
+
+ public void setFloatParam(float floatParam) {
+ this.floatParam = floatParam;
+ }
+
+ public double getDoubleParam() {
+ return this.doubleParam;
+ }
+
+ public void setDoubleParam(double doubleParam) {
+ this.doubleParam = doubleParam;
+ }
+
+ public Object getObjectParam() {
+ return this.objectParam;
+ }
+
+ public void setObjectParam(Object objectParam) {
+ this.objectParam = objectParam;
+ }
+
+ public byte getByteParam() {
+ return this.byteParam;
+ }
+
+ public void setByteParam(byte byteParam) {
+ this.byteParam = byteParam;
+ }
+
+ public short getShortParam() {
+ return this.shortParam;
+ }
+
+ public void setShortParam(short shortParam) {
+ this.shortParam = shortParam;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/SMDActionTest2.java b/plugins/json/src/test/java/org/apache/struts2/json/SMDActionTest2.java
new file mode 100644
index 000000000..f842fc917
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/SMDActionTest2.java
@@ -0,0 +1,58 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import org.apache.struts2.json.annotations.SMD;
+import org.apache.struts2.json.annotations.SMDMethod;
+import org.apache.struts2.json.annotations.SMDMethodParameter;
+
+@SMD(objectName = "testaction", serviceType = "service", version = "10.0")
+public class SMDActionTest2 {
+ private boolean doSomethingInvoked;
+
+ @SMDMethod
+ public void add(@SMDMethodParameter(name = "a")
+ int a, @SMDMethodParameter(name = "b")
+ int b) {
+ }
+
+ @SMDMethod(name = "doSomethingElse")
+ public void doSomething() {
+ doSomethingInvoked = true;
+ }
+
+ @SMDMethod
+ public Bean getBean() {
+ Bean bean = new Bean();
+ bean.setStringField("str");
+ bean.setBooleanField(true);
+ bean.setCharField('s');
+ bean.setDoubleField(10.1);
+ bean.setFloatField(1.5f);
+ bean.setIntField(10);
+ bean.setLongField(100);
+ return bean;
+ }
+
+ public boolean isDoSomethingInvoked() {
+ return doSomethingInvoked;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/SMDMethodInterfaceTest.java b/plugins/json/src/test/java/org/apache/struts2/json/SMDMethodInterfaceTest.java
new file mode 100644
index 000000000..4c098999a
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/SMDMethodInterfaceTest.java
@@ -0,0 +1,195 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import junit.framework.TestCase;
+
+import org.apache.struts2.json.annotations.SMDMethod;
+
+/**
+ * Tests that the SMDMethod annotation can be found in interfaces when
+ * ignoreSMDMethodInterface is false
+ */
+public class SMDMethodInterfaceTest extends TestCase {
+
+ public interface InterfaceA {
+ String getA();
+ }
+
+ public interface InterfaceB {
+ @SMDMethod
+ String getB();
+ }
+
+ public interface InterfaceC {
+ String getC();
+ }
+
+ public interface InterfaceD {
+ String getD();
+ }
+
+ public interface InterfaceE {
+ String getE();
+ }
+
+ public static class ClassA extends ClassB implements InterfaceA {
+
+ private String a;
+ private String z;
+
+ public ClassA(String a, String b, String c, String d, String e, String x, String y, String z) {
+ super(b, c, d, e, x, y);
+ this.a = a;
+ this.z = z;
+ }
+
+ public String getA() {
+ return a;
+ }
+
+ @SMDMethod
+ public String getZ() {
+ return z;
+ }
+ }
+
+ public static class ClassB extends ClassC implements InterfaceB, InterfaceC {
+
+ private String b;
+ private String c;
+ private String y;
+
+ public ClassB(String b, String c, String d, String e, String x, String y) {
+ super(d, e, x);
+ this.b = b;
+ this.c = c;
+ this.y = y;
+ }
+
+ public String getC() {
+ return c;
+ }
+
+ public String getB() {
+ return b;
+ }
+
+ public String getY() {
+ return y;
+ }
+ }
+
+ public static class ClassC implements InterfaceD, InterfaceE {
+
+ private String d;
+ private String e;
+ private String x;
+
+ public ClassC(String d, String e, String x) {
+ this.d = d;
+ this.e = e;
+ this.x = x;
+ }
+
+ public String getD() {
+ return d;
+ }
+
+ public String getE() {
+ return e;
+ }
+
+ @SMDMethod
+ public String getX() {
+ return x;
+ }
+ }
+
+ /**
+ * Asserts that the SMDMethod annotation is only detected on the classes
+ * when ignoreSMDMethodInterfaces is true
+ */
+ public void testBaseClassOnly() {
+ Method[] smdMethodsA = JSONUtil.listSMDMethods(ClassA.class, true);
+ assertEquals(2, smdMethodsA.length);
+ assertEquals("getZ", smdMethodsA[0].getName());
+ assertEquals("getX", smdMethodsA[1].getName());
+
+ Method[] smdMethodsB = JSONUtil.listSMDMethods(ClassB.class, true);
+ assertEquals(1, smdMethodsB.length);
+ assertEquals("getX", smdMethodsB[0].getName());
+
+ Method[] smdMethodsC = JSONUtil.listSMDMethods(ClassC.class, true);
+ assertEquals(1, smdMethodsC.length);
+ assertEquals("getX", smdMethodsC[0].getName());
+ }
+
+ /**
+ * Asserts that the SMDMethod annotation is also detected on the interfaces
+ * and superclasses when ignoreSMDMethodInterfaces is false
+ */
+ public void testInterfaces() {
+ Method[] smdMethodsA = JSONUtil.listSMDMethods(ClassA.class, false);
+ assertEquals(3, smdMethodsA.length);
+ assertEquals("getZ", smdMethodsA[0].getName());
+ assertEquals("getX", smdMethodsA[1].getName());
+ assertEquals("getB", smdMethodsA[2].getName());
+
+ Method[] smdMethodsB = JSONUtil.listSMDMethods(ClassB.class, false);
+ assertEquals(2, smdMethodsB.length);
+ assertEquals("getX", smdMethodsB[0].getName());
+ assertEquals("getB", smdMethodsB[1].getName());
+
+ Method[] smdMethodsC = JSONUtil.listSMDMethods(ClassC.class, false);
+ assertEquals(1, smdMethodsC.length);
+ assertEquals("getX", smdMethodsC[0].getName());
+ }
+
+ /**
+ * This is the important case: detects the SMDMethod annotation on a proxy
+ */
+ public void testWithProxy() {
+
+ InvocationHandler handler = new InvocationHandler() {
+ // dummy implementation
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ return null;
+ }
+ };
+ // proxy is proxy to an impl of ClassA
+ InterfaceA proxy = (InterfaceA) Proxy.newProxyInstance(ClassA.class.getClassLoader(), new Class[] {
+ InterfaceA.class, InterfaceB.class, InterfaceC.class }, handler);
+
+ // first, without the recursion
+ Method[] smdMethodsA = JSONUtil.listSMDMethods(proxy.getClass(), true);
+ assertEquals(0, smdMethodsA.length);
+
+ // now with the recursion
+ Method[] smdMethodsB = JSONUtil.listSMDMethods(proxy.getClass(), false);
+ assertEquals(1, smdMethodsB.length);
+ assertEquals("getB", smdMethodsB[0].getName());
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/SimpleValue.java b/plugins/json/src/test/java/org/apache/struts2/json/SimpleValue.java
new file mode 100644
index 000000000..ed133ee0f
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/SimpleValue.java
@@ -0,0 +1,37 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+public class SimpleValue {
+
+ private String value;
+
+ public SimpleValue() {
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String v) {
+ this.value = v;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/TestAction.java b/plugins/json/src/test/java/org/apache/struts2/json/TestAction.java
new file mode 100644
index 000000000..7251fc4e3
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/TestAction.java
@@ -0,0 +1,188 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.struts2.json.annotations.JSON;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionSupport;
+
+/**
+ */
+@SuppressWarnings("unchecked")
+public class TestAction extends ActionSupport {
+ private static final long serialVersionUID = -8891365561914451494L;
+ private List collection;
+ private List collection2;
+ private Map map;
+ private String foo;
+ private String result;
+ private String[] array;
+ private Bean[] beanArray;
+ private int[] intArray;
+ private List list;
+ private String bar;
+ private String nogetter;
+ private Date date2;
+ private Bean bean;
+ private Date date;
+ private String foo2 = null;
+
+ public Bean getBean() {
+ return this.bean;
+ }
+
+ public void setBean(Bean bean) {
+ this.bean = bean;
+ }
+
+ public List getCollection() {
+ return this.collection;
+ }
+
+ public void setCollection(List collection) {
+ this.collection = collection;
+ }
+
+ public List getCollection2() {
+ return this.collection2;
+ }
+
+ public void setCollection2(List collection2) {
+ this.collection2 = collection2;
+ }
+
+ public Map getMap() {
+ return this.map;
+ }
+
+ public void setMap(Map map) {
+ this.map = map;
+ }
+
+ public String getFoo() {
+ return this.foo;
+ }
+
+ public void setFoo(String foo) {
+ this.foo = foo;
+ }
+
+ public String getResult() {
+ return this.result;
+ }
+
+ public void setResult(String result) {
+ this.result = result;
+ }
+
+ public String[] getArray() {
+ return this.array;
+ }
+
+ public void setArray(String[] array) {
+ this.array = array;
+ }
+
+ public List getList() {
+ return this.list;
+ }
+
+ public void setList(List list) {
+ this.list = list;
+ }
+
+ @Override
+ public String execute() throws Exception {
+ if (this.result == null) {
+ this.result = Action.SUCCESS;
+ }
+
+ return this.result;
+ }
+
+ public String doInput() throws Exception {
+ return INPUT;
+ }
+
+ public void setBar(String bar) {
+ this.bar = bar;
+ }
+
+ @JSON(serialize = false)
+ public String getBar() {
+ return this.bar;
+ }
+
+ public void setNogetter(String nogetter) {
+ this.nogetter = nogetter;
+ }
+
+ @JSON(serialize = false)
+ public int[] getIntArray() {
+ return this.intArray;
+ }
+
+ public void setIntArray(int[] intArray) {
+ this.intArray = intArray;
+ }
+
+ @JSON(serialize = false)
+ public Bean[] getBeanArray() {
+ return this.beanArray;
+ }
+
+ public void setBeanArray(Bean[] beanArray) {
+ this.beanArray = beanArray;
+ }
+
+ public Date getDate() {
+ return this.date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ @JSON(format = "dd/MM/yy")
+ public Date getDate2() {
+ return this.date2;
+ }
+
+ @JSON(format = "dd/MM/yy")
+ public void setDate2(Date date2) {
+ this.date2 = date2;
+ }
+
+ @JSON(serialize = false)
+ public String getFoo2() {
+ return this.foo2;
+ }
+
+ @JSON(deserialize = false)
+ public void setFoo2(String foo2) {
+ this.foo2 = foo2;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/TestAction2.java b/plugins/json/src/test/java/org/apache/struts2/json/TestAction2.java
new file mode 100644
index 000000000..6ccf85a0d
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/TestAction2.java
@@ -0,0 +1,27 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+public class TestAction2 {
+ public String getName() {
+ return "name";
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/TestAction3.java b/plugins/json/src/test/java/org/apache/struts2/json/TestAction3.java
new file mode 100644
index 000000000..516eba22c
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/TestAction3.java
@@ -0,0 +1,27 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+public class TestAction3 extends TestAction2 {
+ public String getName2() {
+ return "name";
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/TestAction4.java b/plugins/json/src/test/java/org/apache/struts2/json/TestAction4.java
new file mode 100644
index 000000000..fa7dcf93b
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/TestAction4.java
@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+public class TestAction4 {
+ private Bean bean;
+
+ public Bean getBean() {
+ if (this.bean == null)
+ this.bean = new Bean();
+ return this.bean;
+ }
+
+ public void setBean(Bean bean) {
+ this.bean = bean;
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/TestUtils.java b/plugins/json/src/test/java/org/apache/struts2/json/TestUtils.java
new file mode 100644
index 000000000..96f6f60e9
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/TestUtils.java
@@ -0,0 +1,95 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+/**
+ * Utility methods for test classes
+ */
+public class TestUtils {
+ /**
+ * normalizes a string so that strings generated on different platforms can
+ * be compared. any group of one or more space, tab, \r, and \n characters
+ * are converted to a single space character
+ *
+ * @param obj
+ * the object to be normalized. normalize will perform its
+ * operation on obj.toString().trim() ;
+ * @param appendSpace
+ * @return the normalized string
+ */
+ public static String normalize(Object obj, boolean appendSpace) {
+ StringTokenizer st = new StringTokenizer(obj.toString().trim(), " \t\r\n");
+ StringBuffer buffer = new StringBuffer(128);
+
+ while (st.hasMoreTokens()) {
+ buffer.append(st.nextToken());
+ }
+
+ return buffer.toString();
+ }
+
+ public static String normalize(URL url) throws Exception {
+ return normalize(readContent(url), true);
+ }
+
+ /**
+ * Attempt to verify the contents of text against the contents of the URL
+ * specified. Performs a trim on both ends
+ *
+ * @param url
+ * the HTML snippet that we want to validate against
+ * @throws Exception
+ * if the validation failed
+ */
+ public static boolean compare(URL url, String text) throws Exception {
+ /**
+ * compare the trimmed values of each buffer and make sure they're
+ * equivalent. however, let's make sure to normalize the strings first
+ * to account for line termination differences between platforms.
+ */
+ String writerString = TestUtils.normalize(text, true);
+ String bufferString = TestUtils.normalize(readContent(url), true);
+
+ return bufferString.equals(writerString);
+ }
+
+ public static String readContent(URL url) throws Exception {
+ if (url == null)
+ throw new Exception("unable to verify a null URL");
+
+ StringBuffer buffer = new StringBuffer(128);
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4096];
+ int nbytes;
+
+ while ((nbytes = in.read(buf)) > 0) {
+ buffer.append(new String(buf, 0, nbytes));
+ }
+
+ in.close();
+
+ return buffer.toString();
+ }
+}
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/WrapperClassBean.java b/plugins/json/src/test/java/org/apache/struts2/json/WrapperClassBean.java
new file mode 100644
index 000000000..65956fc38
--- /dev/null
+++ b/plugins/json/src/test/java/org/apache/struts2/json/WrapperClassBean.java
@@ -0,0 +1,181 @@
+/*
+ * $Id$
+ *
+ * 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.
+ */
+package org.apache.struts2.json;
+
+import java.util.List;
+import java.util.Map;
+
+public class WrapperClassBean {
+
+ private String stringField;
+ private Integer intField;
+ private int nullIntField;
+ private Boolean booleanField;
+ private boolean primitiveBooleanField1;
+ private boolean primitiveBooleanField2;
+ private boolean primitiveBooleanField3;
+ private Character charField;
+ private Long longField;
+ private Float floatField;
+ private Double doubleField;
+ private Object objectField;
+ private Byte byteField;
+ private List listField;
+ private List