Skip to content
This repository was archived by the owner on Dec 15, 2018. It is now read-only.

Commit e15bee8

Browse files
gtudanchkal
authored andcommitted
Resolve CDI named beans in Thymeleaf (#178)
1 parent b64dee3 commit e15bee8

File tree

5 files changed

+239
-33
lines changed

5 files changed

+239
-33
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright © 2017 Ivar Grimstad ([email protected])
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mvcspec.ozark.ext.thymeleaf;
17+
18+
import org.thymeleaf.context.IWebContext;
19+
import org.thymeleaf.context.LazyContextVariable;
20+
21+
import javax.enterprise.context.spi.CreationalContext;
22+
import javax.enterprise.inject.Any;
23+
import javax.enterprise.inject.spi.Bean;
24+
import javax.enterprise.inject.spi.BeanManager;
25+
import javax.enterprise.util.AnnotationLiteral;
26+
import javax.servlet.ServletContext;
27+
import javax.servlet.http.HttpServletRequest;
28+
import javax.servlet.http.HttpServletResponse;
29+
import javax.servlet.http.HttpSession;
30+
import java.util.*;
31+
import java.util.stream.Collectors;
32+
33+
/**
34+
* Thymeleaf context for resolving variables from the model or cdi context. In addition to the functions provided by
35+
* {@link org.thymeleaf.context.WebContext} this allows to resolve beans annotated with {@linkplain javax.inject.Named}
36+
* from the cdi context.
37+
* <p>
38+
* Beans will be resolved in this order:
39+
* <ol>
40+
* <li>special variables (like "request")</li>
41+
* <li>variables from the model</li>
42+
* <li>named beans from the cdi context</li>
43+
* </ol>
44+
* </p>
45+
*
46+
* @author Gregor Tudan
47+
*/
48+
class CDIWebContext implements IWebContext {
49+
50+
private final HttpServletRequest request;
51+
private final HttpServletResponse response;
52+
private final ServletContext context;
53+
private final Locale locale;
54+
55+
private final BeanManager beanManager;
56+
private final Set<String> cdiNamedBeans;
57+
58+
/** keeps track of creational contexts, so beans can get disposed by calling {@link #close()} */
59+
private final Queue<CreationalContext<?>> contexts = new LinkedList<>();
60+
61+
private final Map<String, Object> variables = new HashMap<>();
62+
63+
CDIWebContext(BeanManager beanManager, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, Locale locale) {
64+
this.beanManager = beanManager;
65+
this.request = request;
66+
this.response = response;
67+
this.context = servletContext;
68+
this.locale = locale;
69+
this.cdiNamedBeans = enumerateNamedBeans();
70+
}
71+
72+
@SuppressWarnings("serial")
73+
private Set<String> enumerateNamedBeans() {
74+
Set<Bean<?>> beans = beanManager.getBeans(Object.class, new AnnotationLiteral<Any>() {
75+
});
76+
return beans.stream().map(Bean::getName).filter(Objects::nonNull).collect(Collectors.toSet());
77+
}
78+
79+
@Override
80+
public HttpServletRequest getRequest() {
81+
return request;
82+
}
83+
84+
@Override
85+
public HttpServletResponse getResponse() {
86+
return response;
87+
}
88+
89+
@Override
90+
public HttpSession getSession() {
91+
return request.getSession(false);
92+
}
93+
94+
@Override
95+
public ServletContext getServletContext() {
96+
return context;
97+
}
98+
99+
@Override
100+
public Locale getLocale() {
101+
return locale;
102+
}
103+
104+
@Override
105+
public boolean containsVariable(String name) {
106+
Objects.requireNonNull(name, "The variable name must not be null");
107+
if (variables.containsKey(name)) {
108+
return true;
109+
}
110+
return cdiNamedBeans.contains(name);
111+
}
112+
113+
@Override
114+
public Set<String> getVariableNames() {
115+
final Set<String> variableNames = new HashSet<>(variables.keySet());
116+
variableNames.addAll(this.cdiNamedBeans);
117+
return variableNames;
118+
}
119+
120+
@Override
121+
public Object getVariable(String name) {
122+
Objects.requireNonNull(name, "The variable name must not be null");
123+
if (variables.containsKey(name)) {
124+
return variables.get(name);
125+
} else if (cdiNamedBeans.contains(name)) {
126+
return getCdiBean(name);
127+
} else {
128+
return null;
129+
}
130+
}
131+
132+
private Object getCdiBean(String name) {
133+
return new LazyContextVariable<Object>() {
134+
@Override
135+
protected Object loadValue() {
136+
Bean<?> bean = beanManager.getBeans(name).iterator().next();
137+
CreationalContext ctx = beanManager.createCreationalContext(bean);
138+
// push the context a list so they can be disposed after the template has been processed
139+
contexts.add(ctx);
140+
return beanManager.getReference(bean, bean.getBeanClass(), ctx);
141+
}
142+
};
143+
}
144+
145+
void setVariables(Map<String, Object> variables) {
146+
this.variables.clear();
147+
if (variables != null) {
148+
this.variables.putAll(variables);
149+
}
150+
}
151+
152+
/**
153+
* Dispose all CDI creational contexts to depdenent scoped beans get disposed.
154+
* <p>
155+
* MUST be called after the template has been processed
156+
*/
157+
void close() {
158+
for (CreationalContext ctx : contexts) {
159+
ctx.release();
160+
}
161+
}
162+
}

ext/thymeleaf/src/main/java/org/mvcspec/ozark/ext/thymeleaf/ThymeleafViewEngine.java

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
import org.mvcspec.ozark.engine.ViewEngineBase;
1919
import org.mvcspec.ozark.engine.ViewEngineConfig;
2020
import org.thymeleaf.TemplateEngine;
21-
import org.thymeleaf.context.WebContext;
2221

2322
import javax.enterprise.context.ApplicationScoped;
23+
import javax.enterprise.inject.spi.BeanManager;
2424
import javax.inject.Inject;
2525
import javax.mvc.engine.ViewEngineContext;
2626
import javax.mvc.engine.ViewEngineException;
@@ -35,39 +35,47 @@
3535
* Class Thymeleaf ViewEngine.
3636
*
3737
* @author Rodrigo Turini
38+
* @author Gregor Tudan
3839
*/
3940
@ApplicationScoped
4041
public class ThymeleafViewEngine extends ViewEngineBase {
4142

42-
@Inject
43-
private ServletContext servletContext;
43+
@Inject
44+
private ServletContext servletContext;
4445

45-
@Inject
46-
@ViewEngineConfig
47-
private TemplateEngine engine;
46+
@Inject
47+
private BeanManager beanManager;
4848

49-
@Override
50-
public boolean supports(String view) {
51-
return view.endsWith(".html");
52-
}
49+
@Inject
50+
@ViewEngineConfig
51+
private TemplateEngine engine;
5352

54-
@Override
55-
public void processView(ViewEngineContext context) throws ViewEngineException {
56-
57-
try {
58-
59-
HttpServletRequest request = context.getRequest(HttpServletRequest.class);
60-
HttpServletResponse response = context.getResponse(HttpServletResponse.class);
61-
WebContext ctx = new WebContext(request, response, servletContext, context.getLocale());
53+
@Override
54+
public boolean supports(String view) {
55+
return view.endsWith(".html");
56+
}
57+
58+
@Override
59+
public void processView(ViewEngineContext context) throws ViewEngineException {
60+
try {
61+
HttpServletRequest request = context.getRequest(HttpServletRequest.class);
62+
HttpServletResponse response = context.getResponse(HttpServletResponse.class);
63+
64+
CDIWebContext ctx = new CDIWebContext(beanManager, request, response, servletContext, context.getLocale());
65+
66+
Map<String, Object> model = new HashMap<>(context.getModels().asMap());
67+
model.put("request", request);
68+
ctx.setVariables(model);
69+
70+
try {
71+
engine.process(resolveView(context), ctx, response.getWriter());
72+
response.flushBuffer();
73+
} finally {
74+
ctx.close();
75+
}
76+
} catch (IOException e) {
77+
throw new ViewEngineException(e);
78+
}
79+
}
6280

63-
Map<String, Object> model = new HashMap<>(context.getModels().asMap());
64-
model.put("request", request);
65-
ctx.setVariables(model);
66-
67-
engine.process(resolveView(context), ctx, response.getWriter());
68-
69-
} catch (IOException e) {
70-
throw new ViewEngineException(e);
71-
}
72-
}
7381
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright © 2017 Ivar Grimstad ([email protected])
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mvcspec.ozark.test.thymeleaf;
17+
18+
import javax.enterprise.context.RequestScoped;
19+
import javax.inject.Named;
20+
21+
/**
22+
* @author Gregor Tudan
23+
*/
24+
@RequestScoped
25+
@Named("greeting")
26+
public class Greeting {
27+
28+
private String user;
29+
30+
public String getUser() {
31+
return user;
32+
}
33+
34+
public void setUser(String user) {
35+
this.user = user;
36+
}
37+
}

test/thymeleaf/src/main/java/org/mvcspec/ozark/test/thymeleaf/HelloController.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import javax.inject.Inject;
1919
import javax.mvc.Controller;
20-
import javax.mvc.Models;
2120
import javax.mvc.View;
2221
import javax.ws.rs.GET;
2322
import javax.ws.rs.Path;
@@ -33,13 +32,13 @@
3332
public class HelloController {
3433

3534
@Inject
36-
private Models models;
35+
private Greeting greeting;
3736

3837
@GET
3938
@Controller
4039
@Produces("text/html")
4140
@View("hello.html")
4241
public void hello(@QueryParam("user") String user) {
43-
models.put("user", user);
42+
greeting.setUser(user);
4443
}
4544
}

test/thymeleaf/src/main/webapp/WEB-INF/views/hello.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<html>
1+
<html xmlns:th="http://www.thymeleaf.org">
22
<head>
33
<title>Hello</title>
44
<link rel="stylesheet" type="text/css" th:href="@{~{cp}/ozark.css(cp=${request.contextPath})}" />
55
</head>
66
<body>
7-
<h1>Hello <span th:text="${user}"/>!</h1>
7+
<h1>Hello <span th:text="${greeting.user}"/>!</h1>
88
</body>
99
</html>
1010

0 commit comments

Comments
 (0)