1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.tiles.extras.renderer;
22
23 import java.io.IOException;
24 import java.util.List;
25 import java.util.concurrent.ConcurrentMap;
26 import java.util.concurrent.TimeUnit;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import com.google.common.cache.CacheBuilder;
31 import com.google.common.cache.CacheLoader;
32 import com.google.common.cache.LoadingCache;
33 import org.apache.tiles.Attribute;
34 import org.apache.tiles.ListAttribute;
35 import org.apache.tiles.access.TilesAccess;
36 import org.apache.tiles.request.ApplicationContext;
37 import org.apache.tiles.request.Request;
38 import org.apache.tiles.request.render.Renderer;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public final class OptionsRenderer implements Renderer {
78
79 public static final String CACHE_LIFE_PROPERTY = OptionsRenderer.class.getName() + ".cache_ttl_ms";
80
81 public static final long DEFAULT_CACHE_LIFE = 1000 * 60 * 5;
82
83 public static final Pattern OPTIONS_PATTERN
84 = Pattern.compile(Pattern.quote("{options[") + "(.+)" + Pattern.quote("]}"));
85
86 private static final Logger LOG = LoggerFactory.getLogger(OptionsRenderer.class);
87
88 private final ApplicationContext applicationContext;
89 private final Renderer renderer;
90
91 public OptionsRenderer(final ApplicationContext applicationContext, final Renderer renderer) {
92 this.applicationContext = applicationContext;
93 this.renderer = renderer;
94 }
95
96 @Override
97 public boolean isRenderable(final String path, final Request request) {
98 return renderer.isRenderable(path, request);
99 }
100
101 @Override
102 public void render(final String path, final Request request) throws IOException {
103
104 Matcher matcher = OPTIONS_PATTERN.matcher((String) path);
105
106 if (null != matcher && matcher.find()) {
107 boolean done = false;
108 String match = matcher.group(1);
109 ListAttribute fallbacks = (ListAttribute) TilesAccess
110 .getCurrentContainer(request)
111 .getAttributeContext(request)
112 .getAttribute(match);
113
114 if (null == fallbacks) {
115 throw new IllegalStateException("A matching list-attribute name=\"" + match + "\" must be defined.");
116 } else if (fallbacks.getValue().isEmpty()) {
117 throw new IllegalStateException(
118 "list-attribute name=\"" + match + "\" must have minimum one attribute");
119 }
120
121 for (Attribute option : (List<Attribute>) fallbacks.getValue()) {
122 String template = path.replaceFirst(Pattern.quote(matcher.group()), (String) option.getValue());
123 done = renderAttempt(template, request);
124 if (done) { break; }
125 }
126 if (!done) {
127 throw new IOException("None of the options existed for " + path);
128 }
129 } else {
130 renderer.render(path, request);
131 }
132 }
133
134 private boolean renderAttempt(final String template, final Request request) throws IOException {
135 boolean result = false;
136 if (Cache.attemptTemplate(template)) {
137 try {
138 if (null != applicationContext.getResource(template)) {
139 renderer.render(template, request);
140 result = true;
141 }
142 } catch (IOException ex) {
143 if (ex.getMessage().contains(template)) {
144
145 LOG.trace(ex.getMessage());
146 } else {
147
148 throw ex;
149 }
150 } catch (RuntimeException ex) {
151 if (ex.getMessage().contains(template)) {
152
153 LOG.trace(ex.getMessage());
154 } else {
155
156 throw ex;
157 }
158 }
159 Cache.update(template, result);
160 }
161 return result;
162 }
163
164 private static final class Cache {
165
166 private static final long CACHE_LIFE = Long.getLong(CACHE_LIFE_PROPERTY, DEFAULT_CACHE_LIFE);
167
168
169
170 private static final ConcurrentMap<String,Boolean> TEMPLATE_EXISTS;
171
172 static {
173 LOG.info("cache_ttl_ms=" + CACHE_LIFE);
174
175 LoadingCache<String,Boolean> builder = CacheBuilder
176 .newBuilder()
177 .expireAfterWrite(CACHE_LIFE, TimeUnit.MILLISECONDS)
178 .build(
179 new CacheLoader<String, Boolean>() {
180 @Override
181 public Boolean load(String key) {
182 throw new UnsupportedOperationException(
183 "illegal TEMPLATE_EXISTS.get(\"" + key
184 + "\") before TEMPLATE_EXISTS.containsKey(\"" + key + "\")");
185 }
186 });
187
188 TEMPLATE_EXISTS = builder.asMap();
189 }
190
191
192 static boolean attemptTemplate(final String template) {
193 return !TEMPLATE_EXISTS.containsKey(template) || TEMPLATE_EXISTS.get(template);
194 }
195
196 static void update(final String template, final boolean found) {
197 TEMPLATE_EXISTS.putIfAbsent(template, found);
198 }
199
200 private Cache() {}
201 }
202 }