├── JsonViewExample
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── mangofactory
│ │ └── jsonview
│ │ ├── BaseView.java
│ │ ├── DataView.java
│ │ ├── JsonViewSupportFactoryBean.java
│ │ ├── PojoView.java
│ │ ├── ResponseView.java
│ │ ├── ViewAwareJsonMessageConverter.java
│ │ ├── ViewInjectingReturnValueHandler.java
│ │ └── example
│ │ ├── Book.java
│ │ └── controllers
│ │ └── BookController.java
│ └── webapp
│ ├── WEB-INF
│ ├── log4j.xml
│ ├── spring-servlet.xml
│ └── web.xml
│ └── index.jsp
└── README.md
/JsonViewExample/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.mangofactory.jsonView
5 | JsonViewExample
6 | war
7 | 0.0.1-SNAPSHOT
8 | JsonViewExample
9 |
10 | 3.2.3.RELEASE
11 | 2.2.2
12 |
13 |
14 |
15 | com.springsource.repository.bundles.release
16 | SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases
17 | http://repository.springsource.com/maven/bundles/release
18 |
19 |
20 | sonatype
21 | Sonatype
22 | https://oss.sonatype.org/
23 |
24 |
25 | com.springsource.repository.bundles.external
26 | SpringSource Enterprise Bundle Repository - External Bundle Releases
27 | http://repository.springsource.com/maven/bundles/external
28 |
29 |
30 | com.springsource.repository.libraries.release
31 | SpringSource Enterprise Bundle Repository - SpringSource Library Releases
32 | http://repository.springsource.com/maven/libraries/release
33 |
34 |
35 | spring-milestone
36 | Spring Maven MILESTONE Repository
37 | http://maven.springframework.org/milestone
38 |
39 |
40 | com.springsource.repository.libraries.external
41 | SpringSource Enterprise Bundle Repository - External Library Releases
42 | http://repository.springsource.com/maven/libraries/external
43 |
44 |
45 | com.springsource.repository.snapshot
46 | http://maven.springframework.org/snapshot/
47 |
48 |
49 | com.mysema
50 | QueryDSL repo
51 | http://source.mysema.com/maven2/releases/
52 |
53 |
54 | EclipseLink Repo
55 |
57 | http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo
58 |
59 |
60 | spring-maven-external
61 | Springframework Maven External Repository
62 | http://maven.springframework.org/external
63 |
64 |
65 |
66 |
67 | org.springframework
68 | org.springframework.spring-library
69 | libd
70 | ${spring.version}
71 |
72 |
73 |
74 | com.springsource.org.codehaus.jackson.mapper
75 |
76 | org.codehaus.jackson
77 |
78 |
79 |
80 | com.springsource.com.fasterxml.jackson.core.jackson-core
81 |
82 | com.fasterxml.jackson.core
83 |
84 |
85 |
86 |
87 | javax.servlet
88 | servlet-api
89 | 2.5
90 |
91 |
92 | org.slf4j
93 | slf4j-api
94 | 1.6.1
95 |
96 |
97 | org.slf4j
98 | slf4j-log4j12
99 | 1.6.1
100 |
101 |
102 | org.projectlombok
103 | lombok
104 | 0.11.4
105 | provided
106 |
107 |
108 | com.fasterxml.jackson.core
109 | jackson-core
110 | ${jackson.version}
111 |
112 |
113 | com.fasterxml.jackson.core
114 | jackson-databind
115 | ${jackson.version}
116 |
117 |
118 | com.fasterxml.jackson.core
119 | jackson-annotations
120 | ${jackson.version}
121 |
122 |
123 | commons-logging
124 | commons-logging
125 | 1.1.1
126 |
127 |
128 |
129 |
130 | com.google.guava
131 | guava
132 | 13.0.1
133 |
134 |
135 | http://martypitt.wordpress.com
136 |
137 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/BaseView.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | public interface BaseView {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/DataView.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | public interface DataView {
4 | boolean hasView();
5 | Class extends BaseView> getView();
6 | Object getData();
7 | }
8 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/JsonViewSupportFactoryBean.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | import java.util.List;
4 |
5 | import lombok.extern.slf4j.Slf4j;
6 |
7 | import org.springframework.beans.factory.InitializingBean;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
10 | import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
11 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
12 | import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
13 |
14 | import com.google.common.collect.Lists;
15 |
16 | /**
17 | * Modified Spring 3.1's internal Return value handlers, and wires up a decorator
18 | * to add support for @JsonView
19 | *
20 | * @author martypitt
21 | *
22 | */
23 | @Slf4j
24 | public class JsonViewSupportFactoryBean implements InitializingBean {
25 |
26 | @Autowired
27 | private RequestMappingHandlerAdapter adapter;
28 | @Override
29 | public void afterPropertiesSet() throws Exception {
30 | HandlerMethodReturnValueHandlerComposite returnValueHandlers = adapter.getReturnValueHandlers();
31 | List handlers = Lists.newArrayList(returnValueHandlers.getHandlers());
32 | decorateHandlers(handlers);
33 | adapter.setReturnValueHandlers(handlers);
34 | }
35 | private void decorateHandlers(List handlers) {
36 | for (HandlerMethodReturnValueHandler handler : handlers) {
37 | if (handler instanceof RequestResponseBodyMethodProcessor)
38 | {
39 | ViewInjectingReturnValueHandler decorator = new ViewInjectingReturnValueHandler(handler);
40 | int index = handlers.indexOf(handler);
41 | handlers.set(index, decorator);
42 | log.info("JsonView decorator support wired up");
43 | break;
44 | }
45 | }
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/PojoView.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class PojoView implements DataView {
7 |
8 | private final Object data;
9 | private final Class extends BaseView> view;
10 | @Override
11 | public boolean hasView() {
12 | return true;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/ResponseView.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.RetentionPolicy;
5 |
6 | @Retention(RetentionPolicy.RUNTIME)
7 | public @interface ResponseView {
8 | public Class extends BaseView> value();
9 | }
10 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/ViewAwareJsonMessageConverter.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | import java.io.IOException;
4 |
5 | import org.springframework.http.HttpOutputMessage;
6 | import org.springframework.http.converter.HttpMessageNotWritableException;
7 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
8 | import com.fasterxml.jackson.core.JsonEncoding;
9 | import com.fasterxml.jackson.core.JsonGenerator;
10 | import com.fasterxml.jackson.databind.MapperFeature;
11 | import com.fasterxml.jackson.databind.ObjectMapper;
12 | import com.fasterxml.jackson.databind.ObjectWriter;
13 |
14 | /**
15 | * Adds support for Jackson's JsonView on methods
16 | * annotated with a {@link ResponseView} annotation
17 | * @author martypitt
18 | *
19 | */
20 | public class ViewAwareJsonMessageConverter extends
21 | MappingJackson2HttpMessageConverter {
22 |
23 | public ViewAwareJsonMessageConverter()
24 | {
25 | super();
26 | ObjectMapper defaultMapper = new ObjectMapper();
27 | defaultMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
28 | setObjectMapper(defaultMapper);
29 | }
30 |
31 | @Override
32 | protected void writeInternal(Object object, HttpOutputMessage outputMessage)
33 | throws IOException, HttpMessageNotWritableException {
34 | if (object instanceof DataView && ((DataView) object).hasView())
35 | {
36 | writeView((DataView) object, outputMessage);
37 | } else {
38 | super.writeInternal(object, outputMessage);
39 | }
40 | }
41 | protected void writeView(DataView view, HttpOutputMessage outputMessage)
42 | throws IOException, HttpMessageNotWritableException {
43 | JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
44 | ObjectWriter writer = getWriterForView(view.getView());
45 | JsonGenerator jsonGenerator =
46 | writer.getFactory().createGenerator(outputMessage.getBody(), encoding);
47 | try {
48 | writer.writeValue(jsonGenerator, view.getData());
49 | }
50 | catch (IOException ex) {
51 | throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
52 | }
53 | }
54 |
55 | private ObjectWriter getWriterForView(Class> view) {
56 | ObjectMapper mapper = new ObjectMapper();
57 | mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
58 | return mapper.writer().withView(view);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/ViewInjectingReturnValueHandler.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview;
2 |
3 | import org.springframework.core.MethodParameter;
4 | import org.springframework.web.context.request.NativeWebRequest;
5 | import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
6 | import org.springframework.web.method.support.ModelAndViewContainer;
7 |
8 | /**
9 | * Decorator that detects a declared {@link ResponseView}, and
10 | * injects support if required
11 | * @author martypitt
12 | *
13 | */
14 | public class ViewInjectingReturnValueHandler implements
15 | HandlerMethodReturnValueHandler {
16 |
17 | private final HandlerMethodReturnValueHandler delegate;
18 |
19 | public ViewInjectingReturnValueHandler(HandlerMethodReturnValueHandler delegate)
20 | {
21 | this.delegate = delegate;
22 | }
23 | @Override
24 | public boolean supportsReturnType(MethodParameter returnType) {
25 | return delegate.supportsReturnType(returnType);
26 | }
27 |
28 | @Override
29 | public void handleReturnValue(Object returnValue,
30 | MethodParameter returnType, ModelAndViewContainer mavContainer,
31 | NativeWebRequest webRequest) throws Exception {
32 |
33 | Class extends BaseView> viewClass = getDeclaredViewClass(returnType);
34 | if (viewClass != null)
35 | {
36 | returnValue = wrapResult(returnValue,viewClass);
37 | }
38 |
39 | delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
40 | }
41 | /**
42 | * Returns the view class declared on the method, if it exists.
43 | * Otherwise, returns null.
44 | * @param returnType
45 | * @return
46 | */
47 | private Class extends BaseView> getDeclaredViewClass(MethodParameter returnType) {
48 | ResponseView annotation = returnType.getMethodAnnotation(ResponseView.class);
49 | if (annotation != null)
50 | {
51 | return annotation.value();
52 | } else {
53 | return null;
54 | }
55 | }
56 | private Object wrapResult(Object result, Class extends BaseView> viewClass) {
57 | PojoView response = new PojoView(result, viewClass);
58 | return response;
59 | }
60 | }
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/example/Book.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview.example;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import com.fasterxml.jackson.annotation.JsonView;
8 |
9 | import com.mangofactory.jsonview.BaseView;
10 |
11 | @Data @NoArgsConstructor @AllArgsConstructor
12 | public class Book {
13 |
14 | @JsonView(SummaryView.class)
15 | private String title;
16 | @JsonView(SummaryView.class)
17 | private String author;
18 | private String review;
19 | @JsonView(BaseView.class)
20 | private Integer id;
21 |
22 | public static interface SummaryView extends BaseView {}
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/java/com/mangofactory/jsonview/example/controllers/BookController.java:
--------------------------------------------------------------------------------
1 | package com.mangofactory.jsonview.example.controllers;
2 |
3 | import java.util.List;
4 |
5 | import org.springframework.stereotype.Controller;
6 | import org.springframework.web.bind.annotation.PathVariable;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.ResponseBody;
9 |
10 | import com.google.common.collect.Lists;
11 | import com.mangofactory.jsonview.ResponseView;
12 | import com.mangofactory.jsonview.example.Book;
13 | import com.mangofactory.jsonview.example.Book.SummaryView;
14 |
15 | @Controller
16 | public class BookController {
17 |
18 | List data;
19 |
20 | public BookController()
21 | {
22 | data = Lists.newArrayList(
23 | new Book("Effective Java","Joshua Bloch","Essential",1),
24 | new Book("Breaking Dawn","Stephanie Myers","Just terrible",2)
25 | );
26 | }
27 | @RequestMapping("/books")
28 | public @ResponseBody List getBooks()
29 | {
30 | return data;
31 | }
32 | @RequestMapping("/books/summaries")
33 | @ResponseView(SummaryView.class)
34 | public @ResponseBody List getBookSummaries()
35 | {
36 | return data;
37 | }
38 | @RequestMapping("/book/{id}/summary")
39 | @ResponseView(SummaryView.class)
40 | public @ResponseBody Book getSummary(@PathVariable("id") Integer id)
41 | {
42 | return data.get(id - 1);
43 | }
44 | @RequestMapping("/book/{id}")
45 | public @ResponseBody Book getDetail(@PathVariable("id") Integer id)
46 | {
47 | return data.get(id - 1);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/webapp/WEB-INF/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/webapp/WEB-INF/spring-servlet.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | JsonView Example
7 |
8 | log4jConfigLocation
9 | /WEB-INF/log4j.xml
10 |
11 |
12 | org.springframework.web.util.Log4jConfigListener
13 |
14 |
15 | spring
16 | org.springframework.web.servlet.DispatcherServlet
17 | 1
18 |
19 |
20 | spring
21 | /*
22 |
23 |
24 |
--------------------------------------------------------------------------------
/JsonViewExample/src/main/webapp/index.jsp:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello World!
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JsonViewExample
2 | ===============
3 |
4 | Support for custom Jackson @JsonView within Spring MVC
5 |
6 | Allows per-method support of custom views within Spring MVC methods, by annotating with `@ResponseView`.
7 |
8 | For example:
9 |
10 | @RequestMapping("/books")
11 | public @ResponseBody List getBooks()
12 | {
13 | return data;
14 | }
15 | @RequestMapping("/books/summaries")
16 | @ResponseView(SummaryView.class)
17 | public @ResponseBody List getBookSummaries()
18 | {
19 | return data;
20 | }
21 |
22 | In the above scenario, calls to `/books` recieve the entire `Book` model, while calls to `/books/summaries` only recieve properties annotated with Jackson's `@JsonView(SummaryView.class)` annotation.
23 |
24 | See http://martypitt.wordpress.com/2012/11/05/custom-json-views-with-spring-mvc-and-jackson/ for details.
25 |
26 |
--------------------------------------------------------------------------------