/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.view.json;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ScriptableObject;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.http.MediaType;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.View;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Jeremy Grelle
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class MappingJackson2JsonViewTests {
private MappingJackson2JsonView view;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private Context jsContext;
private ScriptableObject jsScope;
@Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
jsContext = ContextFactory.getGlobal().enterContext();
jsScope = jsContext.initStandardObjects();
view = new MappingJackson2JsonView();
}
@Test
public void isExposePathVars() {
assertEquals("Must not expose path variables", false, view.isExposePathVariables());
}
@Test
public void renderSimpleMap() throws Exception {
Map<String, Object> model = new HashMap<>();
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
model.put("foo", "bar");
view.setUpdateContentLength(true);
view.render(model, request, response);
assertEquals("no-store", response.getHeader("Cache-Control"));
assertEquals(MappingJackson2JsonView.DEFAULT_CONTENT_TYPE, response.getContentType());
String jsonResult = response.getContentAsString();
assertTrue(jsonResult.length() > 0);
assertEquals(jsonResult.length(), response.getContentLength());
validateResult();
}
@Test
public void renderWithSelectedContentType() throws Exception {
Map<String, Object> model = new HashMap<>();
model.put("foo", "bar");
view.render(model, request, response);
assertEquals("application/json", response.getContentType());
request.setAttribute(View.SELECTED_CONTENT_TYPE, new MediaType("application", "vnd.example-v2+xml"));
view.render(model, request, response);
assertEquals("application/vnd.example-v2+xml", response.getContentType());
}
@Test
public void renderCaching() throws Exception {
view.setDisableCaching(false);
Map<String, Object> model = new HashMap<>();
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
model.put("foo", "bar");
view.render(model, request, response);
assertNull(response.getHeader("Cache-Control"));
}
@Test
public void renderSimpleMapPrefixed() throws Exception {
view.setPrefixJson(true);
renderSimpleMap();
}
@Test
public void renderSimpleBean() throws Exception {
Object bean = new TestBeanSimple();
Map<String, Object> model = new HashMap<>();
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
model.put("foo", bean);
view.setUpdateContentLength(true);
view.render(model, request, response);
assertTrue(response.getContentAsString().length() > 0);
assertEquals(response.getContentAsString().length(), response.getContentLength());
validateResult();
}
@Test
public void renderWithPrettyPrint() throws Exception {
ModelMap model = new ModelMap("foo", new TestBeanSimple());
view.setPrettyPrint(true);
view.render(model, request, response);
String result = response.getContentAsString().replace("\r\n", "\n");
assertTrue("Pretty printing not applied:\n" + result, result.startsWith("{\n \"foo\" : {\n "));
validateResult();
}
@Test
public void renderSimpleBeanPrefixed() throws Exception {
view.setPrefixJson(true);
renderSimpleBean();
assertTrue(response.getContentAsString().startsWith(")]}', "));
}
@Test
public void renderSimpleBeanNotPrefixed() throws Exception {
view.setPrefixJson(false);
renderSimpleBean();
assertFalse(response.getContentAsString().startsWith(")]}', "));
}
@Test
public void renderWithCustomSerializerLocatedByAnnotation() throws Exception {
Object bean = new TestBeanSimpleAnnotated();
Map<String, Object> model = new HashMap<>();
model.put("foo", bean);
view.render(model, request, response);
assertTrue(response.getContentAsString().length() > 0);
assertEquals("{\"foo\":{\"testBeanSimple\":\"custom\"}}", response.getContentAsString());
validateResult();
}
@Test
public void renderWithCustomSerializerLocatedByFactory() throws Exception {
SerializerFactory factory = new DelegatingSerializerFactory(null);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializerFactory(factory);
view.setObjectMapper(mapper);
Object bean = new TestBeanSimple();
Map<String, Object> model = new HashMap<>();
model.put("foo", bean);
model.put("bar", new TestChildBean());
view.render(model, request, response);
String result = response.getContentAsString();
assertTrue(result.length() > 0);
assertTrue(result.contains("\"foo\":{\"testBeanSimple\":\"custom\"}"));
validateResult();
}
@Test
public void renderOnlyIncludedAttributes() throws Exception {
Set<String> attrs = new HashSet<>();
attrs.add("foo");
attrs.add("baz");
attrs.add("nil");
view.setModelKeys(attrs);
Map<String, Object> model = new HashMap<>();
model.put("foo", "foo");
model.put("bar", "bar");
model.put("baz", "baz");
view.render(model, request, response);
String result = response.getContentAsString();
assertTrue(result.length() > 0);
assertTrue(result.contains("\"foo\":\"foo\""));
assertTrue(result.contains("\"baz\":\"baz\""));
validateResult();
}
@Test
public void filterSingleKeyModel() throws Exception {
view.setExtractValueFromSingleKeyModel(true);
Map<String, Object> model = new HashMap<>();
TestBeanSimple bean = new TestBeanSimple();
model.put("foo", bean);
Object actual = view.filterModel(model);
assertSame(bean, actual);
}
@SuppressWarnings("rawtypes")
@Test
public void filterTwoKeyModel() throws Exception {
view.setExtractValueFromSingleKeyModel(true);
Map<String, Object> model = new HashMap<>();
TestBeanSimple bean1 = new TestBeanSimple();
TestBeanSimple bean2 = new TestBeanSimple();
model.put("foo1", bean1);
model.put("foo2", bean2);
Object actual = view.filterModel(model);
assertTrue(actual instanceof Map);
assertSame(bean1, ((Map) actual).get("foo1"));
assertSame(bean2, ((Map) actual).get("foo2"));
}
@Test
public void renderSimpleBeanWithJsonView() throws Exception {
Object bean = new TestBeanSimple();
Map<String, Object> model = new HashMap<>();
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
model.put("foo", bean);
model.put(JsonView.class.getName(), MyJacksonView1.class);
view.setUpdateContentLength(true);
view.render(model, request, response);
String content = response.getContentAsString();
assertTrue(content.length() > 0);
assertEquals(content.length(), response.getContentLength());
assertTrue(content.contains("foo"));
assertFalse(content.contains("boo"));
assertFalse(content.contains(JsonView.class.getName()));
}
@Test
public void renderSimpleBeanWithFilters() throws Exception {
TestSimpleBeanFiltered bean = new TestSimpleBeanFiltered();
bean.setProperty1("value");
bean.setProperty2("value");
Map<String, Object> model = new HashMap<>();
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
model.put("foo", bean);
FilterProvider filters = new SimpleFilterProvider().addFilter("myJacksonFilter",
SimpleBeanPropertyFilter.serializeAllExcept("property2"));
model.put(FilterProvider.class.getName(), filters);
view.setUpdateContentLength(true);
view.render(model, request, response);
String content = response.getContentAsString();
assertTrue(content.length() > 0);
assertEquals(content.length(), response.getContentLength());
assertThat(content, containsString("\"property1\":\"value\""));
assertThat(content, not(containsString("\"property2\":\"value\"")));
assertFalse(content.contains(FilterProvider.class.getName()));
}
@Test
public void renderWithJsonp() throws Exception {
testJsonp("jsonp", "callback", true);
testJsonp("jsonp", "_callback", true);
testJsonp("jsonp", "_Call.bAcK", true);
testJsonp("jsonp", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
testJsonp("jsonp", "<script>", false);
testJsonp("jsonp", "!foo!bar", false);
}
private void validateResult() throws Exception {
String json = response.getContentAsString();
DirectFieldAccessor viewAccessor = new DirectFieldAccessor(view);
String jsonPrefix = (String)viewAccessor.getPropertyValue("jsonPrefix");
if (jsonPrefix != null) {
json = json.substring(5);
}
Object jsResult =
jsContext.evaluateString(jsScope, "(" + json + ")", "JSON Stream", 1, null);
assertNotNull("Json Result did not eval as valid JavaScript", jsResult);
assertEquals("application/json", response.getContentType());
}
private void testJsonp(String paramName, String paramValue, boolean validValue) throws Exception {
Map<String, Object> model = new HashMap<>();
model.put("foo", "bar");
this.request = new MockHttpServletRequest();
this.request.addParameter("otherparam", "value");
this.request.addParameter(paramName, paramValue);
this.response = new MockHttpServletResponse();
this.view.render(model, this.request, this.response);
String content = this.response.getContentAsString();
if (validValue) {
assertEquals("/**/" + paramValue + "({\"foo\":\"bar\"});", content);
}
else {
assertEquals("{\"foo\":\"bar\"}", content);
}
}
public interface MyJacksonView1 {
}
public interface MyJacksonView2 {
}
@SuppressWarnings("unused")
public static class TestBeanSimple {
@JsonView(MyJacksonView1.class)
private String property1 = "foo";
private boolean test = false;
@JsonView(MyJacksonView2.class)
private String property2 = "boo";
private TestChildBean child = new TestChildBean();
public String getProperty1() {
return property1;
}
public boolean getTest() {
return test;
}
public String getProperty2() {
return property2;
}
public Date getNow() {
return new Date();
}
public TestChildBean getChild() {
return child;
}
}
@JsonSerialize(using=TestBeanSimpleSerializer.class)
public static class TestBeanSimpleAnnotated extends TestBeanSimple {
}
public static class TestChildBean {
private String value = "bar";
private String baz = null;
private TestBeanSimple parent = null;
public String getValue() {
return value;
}
public String getBaz() {
return baz;
}
public TestBeanSimple getParent() {
return parent;
}
public void setParent(TestBeanSimple parent) {
this.parent = parent;
}
}
public static class TestBeanSimpleSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName("testBeanSimple");
jgen.writeString("custom");
jgen.writeEndObject();
}
}
@JsonFilter("myJacksonFilter")
private static class TestSimpleBeanFiltered {
private String property1;
private String property2;
public String getProperty1() {
return property1;
}
public void setProperty1(String property1) {
this.property1 = property1;
}
public String getProperty2() {
return property2;
}
public void setProperty2(String property2) {
this.property2 = property2;
}
}
@SuppressWarnings("serial")
public static class DelegatingSerializerFactory extends BeanSerializerFactory {
protected DelegatingSerializerFactory(SerializerFactoryConfig config) {
super(config);
}
@Override
public JsonSerializer<Object> createSerializer(SerializerProvider prov, JavaType type) throws JsonMappingException {
if (type.getRawClass() == TestBeanSimple.class) {
return new TestBeanSimpleSerializer();
}
else {
return super.createSerializer(prov, type);
}
}
}
}