Continued work on TemplateLanguage configuration and new file extensions: Renamed...
[freemarker.git] / freemarker-core-test / src / test / java / org / apache / freemarker / core / templateresolver / DefaultTemplateResolverTest.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.freemarker.core.templateresolver;
21
22 import static org.hamcrest.Matchers.*;
23 import static org.junit.Assert.*;
24
25 import java.io.IOException;
26 import java.io.Serializable;
27 import java.nio.charset.StandardCharsets;
28 import java.util.Locale;
29
30 import org.apache.freemarker.core.Configuration;
31 import org.apache.freemarker.core.Template;
32 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
33 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
34 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
35 import org.apache.freemarker.core.templateresolver.impl.StrongCacheStorage;
36 import org.apache.freemarker.test.MonitoredTemplateLoader;
37 import org.apache.freemarker.test.MonitoredTemplateLoader.CloseSessionEvent;
38 import org.apache.freemarker.test.MonitoredTemplateLoader.CreateSessionEvent;
39 import org.apache.freemarker.test.MonitoredTemplateLoader.LoadEvent;
40 import org.apache.freemarker.test.TestConfigurationBuilder;
41 import org.hamcrest.Matchers;
42 import org.junit.Test;
43
44 import com.google.common.collect.ImmutableList;
45
46 public class DefaultTemplateResolverTest {
47
48 @Test
49 public void testCachedException() throws Exception {
50 MockTemplateLoader loader = new MockTemplateLoader();
51 Configuration cfg = new TestConfigurationBuilder()
52 .templateLoader(loader)
53 .templateCacheStorage(new StrongCacheStorage())
54 .templateUpdateDelayMilliseconds(100L)
55 .build();
56 TemplateResolver tr = cfg.getTemplateResolver();
57 assertThat(tr, instanceOf(DefaultTemplateResolver.class));
58
59 loader.setThrowException(true);
60 try {
61 tr.getTemplate("t", Locale.getDefault(), null).getTemplate();
62 fail();
63 } catch (IOException e) {
64 assertEquals("mock IO exception", e.getMessage());
65 assertEquals(1, loader.getLoadAttemptCount());
66 try {
67 tr.getTemplate("t", Locale.getDefault(), null).getTemplate();
68 fail();
69 } catch (IOException e2) {
70 // Still 1 - returned cached exception
71 assertThat(e2.getMessage(),
72 Matchers.allOf(Matchers.containsString("There was an error loading the template on an " +
73 "earlier attempt")));
74 assertSame(e, e2.getCause());
75 assertEquals(1, loader.getLoadAttemptCount());
76 try {
77 Thread.sleep(132L);
78 tr.getTemplate("t", Locale.getDefault(), null).getTemplate();
79 fail();
80 } catch (IOException e3) {
81 // Cache had to retest
82 assertEquals("mock IO exception", e.getMessage());
83 assertEquals(2, loader.getLoadAttemptCount());
84 }
85 }
86 }
87 }
88
89 @Test
90 public void testCachedNotFound() throws Exception {
91 MockTemplateLoader loader = new MockTemplateLoader();
92 Configuration cfg = new TestConfigurationBuilder()
93 .templateLoader(loader)
94 .templateCacheStorage(new StrongCacheStorage())
95 .templateUpdateDelayMilliseconds(100L)
96 .localizedTemplateLookup(false)
97 .build();
98 TemplateResolver tr = cfg.getTemplateResolver();
99 assertThat(tr, instanceOf(DefaultTemplateResolver.class));
100
101 assertNull(tr.getTemplate("t", Locale.getDefault(), null).getTemplate());
102 assertEquals(1, loader.getLoadAttemptCount());
103 assertNull(tr.getTemplate("t", Locale.getDefault(), null).getTemplate());
104 // Still 1 - returned cached exception
105 assertEquals(1, loader.getLoadAttemptCount());
106 Thread.sleep(132L);
107 assertNull(tr.getTemplate("t", Locale.getDefault(), null).getTemplate());
108 // Cache had to retest
109 assertEquals(2, loader.getLoadAttemptCount());
110 }
111
112 private static class MockTemplateLoader implements TemplateLoader {
113 private boolean throwException;
114 private int loadAttemptCount;
115
116 public void setThrowException(boolean throwException) {
117 this.throwException = throwException;
118 }
119
120 public int getLoadAttemptCount() {
121 return loadAttemptCount;
122 }
123
124 @Override
125 public TemplateLoaderSession createSession() {
126 return null;
127 }
128
129 @Override
130 public TemplateLoadingResult load(String name, TemplateLoadingSource ifSourceDiffersFrom,
131 Serializable ifVersionDiffersFrom, TemplateLoaderSession session) throws IOException {
132 ++loadAttemptCount;
133 if (throwException) {
134 throw new IOException("mock IO exception");
135 }
136 return TemplateLoadingResult.NOT_FOUND;
137 }
138
139 @Override
140 public void resetState() {
141 //
142 }
143
144 }
145
146 @Test
147 public void testManualRemovalPlain() throws Exception {
148 StringTemplateLoader loader = new StringTemplateLoader();
149 Configuration cfg = new TestConfigurationBuilder()
150 .templateCacheStorage(new StrongCacheStorage())
151 .templateLoader(loader)
152 .templateUpdateDelayMilliseconds(Long.MAX_VALUE)
153 .build();
154
155 loader.putTemplate("1.f3ah", "1 v1");
156 loader.putTemplate("2.f3ah", "2 v1");
157 assertEquals("1 v1", cfg.getTemplate("1.f3ah").toString());
158 assertEquals("2 v1", cfg.getTemplate("2.f3ah").toString());
159
160 loader.putTemplate("1.f3ah", "1 v2");
161 loader.putTemplate("2.f3ah", "2 v2");
162 assertEquals("1 v1", cfg.getTemplate("1.f3ah").toString()); // no change
163 assertEquals("2 v1", cfg.getTemplate("2.f3ah").toString()); // no change
164
165 cfg.removeTemplateFromCache("1.f3ah", cfg.getLocale(), null);
166 assertEquals("1 v2", cfg.getTemplate("1.f3ah").toString()); // changed
167 assertEquals("2 v1", cfg.getTemplate("2.f3ah").toString());
168
169 cfg.removeTemplateFromCache("2.f3ah", cfg.getLocale(), null);
170 assertEquals("1 v2", cfg.getTemplate("1.f3ah").toString());
171 assertEquals("2 v2", cfg.getTemplate("2.f3ah").toString()); // changed
172 }
173
174 @Test
175 public void testManualRemovalI18ed() throws Exception {
176 StringTemplateLoader loader = new StringTemplateLoader();
177 Configuration cfg = new TestConfigurationBuilder()
178 .templateCacheStorage(new StrongCacheStorage())
179 .templateLoader(loader)
180 .templateUpdateDelayMilliseconds(Long.MAX_VALUE)
181 .build();
182
183 loader.putTemplate("1_en_US.f3ah", "1_en_US v1");
184 loader.putTemplate("1_en.f3ah", "1_en v1");
185 loader.putTemplate("1.f3ah", "1 v1");
186
187 assertEquals("1_en_US v1", cfg.getTemplate("1.f3ah").toString());
188 assertEquals("1_en v1", cfg.getTemplate("1.f3ah", Locale.UK).toString());
189 assertEquals("1 v1", cfg.getTemplate("1.f3ah", Locale.GERMANY).toString());
190
191 loader.putTemplate("1_en_US.f3ah", "1_en_US v2");
192 loader.putTemplate("1_en.f3ah", "1_en v2");
193 loader.putTemplate("1.f3ah", "1 v2");
194 assertEquals("1_en_US v1", cfg.getTemplate("1.f3ah").toString());
195 assertEquals("1_en v1", cfg.getTemplate("1.f3ah", Locale.UK).toString());
196 assertEquals("1 v1", cfg.getTemplate("1.f3ah", Locale.GERMANY).toString());
197
198 cfg.removeTemplateFromCache("1.f3ah", cfg.getLocale(), null);
199 assertEquals("1_en_US v2", cfg.getTemplate("1.f3ah").toString());
200 assertEquals("1_en v1", cfg.getTemplate("1.f3ah", Locale.UK).toString());
201 assertEquals("1 v1", cfg.getTemplate("1.f3ah", Locale.GERMANY).toString());
202 assertEquals("1 v2", cfg.getTemplate("1.f3ah", Locale.ITALY).toString());
203
204 cfg.removeTemplateFromCache("1.f3ah", Locale.GERMANY, null);
205 assertEquals("1_en v1", cfg.getTemplate("1.f3ah", Locale.UK).toString());
206 assertEquals("1 v2", cfg.getTemplate("1.f3ah", Locale.GERMANY).toString());
207
208 cfg.removeTemplateFromCache("1.f3ah", Locale.CANADA, null);
209 assertEquals("1_en v1", cfg.getTemplate("1.f3ah", Locale.UK).toString());
210
211 cfg.removeTemplateFromCache("1.f3ah", Locale.UK, null);
212 assertEquals("1_en v2", cfg.getTemplate("1.f3ah", Locale.UK).toString());
213 }
214
215 @Test
216 public void testZeroUpdateDelay() throws Exception {
217 MonitoredTemplateLoader loader = new MonitoredTemplateLoader();
218
219 {
220 Configuration cfg = new TestConfigurationBuilder()
221 .templateCacheStorage(new StrongCacheStorage())
222 .templateLoader(loader)
223 .templateUpdateDelayMilliseconds(0L)
224 .build();
225 for (int i = 1; i <= 3; i++) {
226 loader.putTextTemplate("t.f3ah", "v" + i);
227 assertEquals("v" + i, cfg.getTemplate("t.f3ah").toString());
228 }
229
230 loader.clearEvents();
231 loader.putTextTemplate("t.f3ah", "v8");
232 assertEquals("v8", cfg.getTemplate("t.f3ah").toString());
233 assertEquals("v8", cfg.getTemplate("t.f3ah").toString());
234 loader.putTextTemplate("t.f3ah", "v9");
235 assertEquals("v9", cfg.getTemplate("t.f3ah").toString());
236 assertEquals("v9", cfg.getTemplate("t.f3ah").toString());
237 assertEquals(
238 ImmutableList.of(
239 new LoadEvent("t_en_US.f3ah", TemplateLoadingResultStatus.NOT_FOUND), // v8
240 new LoadEvent("t_en.f3ah", TemplateLoadingResultStatus.NOT_FOUND),
241 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.OPENED),
242
243 new LoadEvent("t_en_US.f3ah", TemplateLoadingResultStatus.NOT_FOUND), // v8
244 new LoadEvent("t_en.f3ah", TemplateLoadingResultStatus.NOT_FOUND),
245 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.NOT_MODIFIED),
246
247 new LoadEvent("t_en_US.f3ah", TemplateLoadingResultStatus.NOT_FOUND), // v9
248 new LoadEvent("t_en.f3ah", TemplateLoadingResultStatus.NOT_FOUND),
249 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.OPENED),
250
251 new LoadEvent("t_en_US.f3ah", TemplateLoadingResultStatus.NOT_FOUND), // v9
252 new LoadEvent("t_en.f3ah", TemplateLoadingResultStatus.NOT_FOUND),
253 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.NOT_MODIFIED)
254 ),
255 loader.getEvents(LoadEvent.class));
256 }
257
258 {
259 Configuration cfg = new TestConfigurationBuilder()
260 .templateCacheStorage(new StrongCacheStorage())
261 .templateLoader(loader)
262 .templateUpdateDelayMilliseconds(0L)
263 .localizedTemplateLookup(false)
264 .build();
265 loader.clearEvents();
266 loader.putTextTemplate("t.f3ah", "v10");
267 assertEquals("v10", cfg.getTemplate("t.f3ah").toString());
268 loader.putTextTemplate("t.f3ah", "v11"); // same time stamp, different content
269 assertEquals("v11", cfg.getTemplate("t.f3ah").toString());
270 assertEquals("v11", cfg.getTemplate("t.f3ah").toString());
271 assertEquals("v11", cfg.getTemplate("t.f3ah").toString());
272 Thread.sleep(17L);
273 assertEquals("v11", cfg.getTemplate("t.f3ah").toString());
274 loader.putTextTemplate("t.f3ah", "v12");
275 assertEquals("v12", cfg.getTemplate("t.f3ah").toString());
276 assertEquals("v12", cfg.getTemplate("t.f3ah").toString());
277 assertEquals(
278 ImmutableList.of(
279 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.OPENED), // v10
280 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.OPENED), // v11
281 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.NOT_MODIFIED),
282 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.NOT_MODIFIED),
283 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.NOT_MODIFIED),
284 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.OPENED), // v12
285 new LoadEvent("t.f3ah", TemplateLoadingResultStatus.NOT_MODIFIED)
286 ),
287 loader.getEvents(LoadEvent.class));
288 }
289 }
290
291 @Test
292 public void testWrongEncodingReload() throws Exception {
293 MonitoredTemplateLoader loader = new MonitoredTemplateLoader();
294 loader.putBinaryTemplate("utf-8_en.f3ah", "<#ftl encoding='utf-8'>Béka");
295 loader.putBinaryTemplate("utf-8.f3ah", "Bar");
296 loader.putBinaryTemplate("iso-8859-1_en_US.f3ah", "<#ftl encoding='ISO-8859-1'>Béka",
297 StandardCharsets.ISO_8859_1, "v1");
298 Configuration cfg = new TestConfigurationBuilder().templateLoader(loader).build();
299
300 {
301 Template t = cfg.getTemplate("utf-8.f3ah");
302 assertEquals("utf-8.f3ah", t.getLookupName());
303 assertEquals("utf-8_en.f3ah", t.getSourceName());
304 assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
305 assertEquals("Béka", t.toString());
306
307 assertEquals(
308 ImmutableList.of(
309 CreateSessionEvent.INSTANCE,
310 new LoadEvent("utf-8_en_US.f3ah", TemplateLoadingResultStatus.NOT_FOUND),
311 new LoadEvent("utf-8_en.f3ah", TemplateLoadingResultStatus.OPENED),
312 CloseSessionEvent.INSTANCE),
313 loader.getEvents());
314 }
315
316 {
317 loader.clearEvents();
318
319 Template t = cfg.getTemplate("iso-8859-1.f3ah");
320 assertEquals("iso-8859-1.f3ah", t.getLookupName());
321 assertEquals("iso-8859-1_en_US.f3ah", t.getSourceName());
322 assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
323 assertEquals("Béka", t.toString());
324
325 assertEquals(
326 ImmutableList.of(
327 CreateSessionEvent.INSTANCE,
328 new LoadEvent("iso-8859-1_en_US.f3ah", TemplateLoadingResultStatus.OPENED),
329 CloseSessionEvent.INSTANCE),
330 loader.getEvents());
331 }
332 }
333
334 @Test
335 public void testNoWrongEncodingForTemplateLoader2WithReader() throws Exception {
336 MonitoredTemplateLoader loader = new MonitoredTemplateLoader();
337 loader.putTextTemplate("foo_en.f3ah", "<#ftl encoding='utf-8'>ő");
338 loader.putTextTemplate("foo.f3ah", "B");
339 Configuration cfg = new TestConfigurationBuilder().templateLoader(loader).build();
340
341 {
342 Template t = cfg.getTemplate("foo.f3ah");
343 assertEquals("foo.f3ah", t.getLookupName());
344 assertEquals("foo_en.f3ah", t.getSourceName());
345 assertNull(t.getActualSourceEncoding());
346 assertEquals("ő", t.toString());
347
348 assertEquals(
349 ImmutableList.of(
350 CreateSessionEvent.INSTANCE,
351 new LoadEvent("foo_en_US.f3ah", TemplateLoadingResultStatus.NOT_FOUND),
352 new LoadEvent("foo_en.f3ah", TemplateLoadingResultStatus.OPENED),
353 CloseSessionEvent.INSTANCE),
354 loader.getEvents());
355 }
356 }
357
358 @Test
359 public void testTemplateNameFormatException() throws Exception {
360 Configuration cfg = new TestConfigurationBuilder()
361 .templateNameFormat(DefaultTemplateNameFormat.INSTANCE)
362 .build();
363 try {
364 cfg.getTemplate("../x");
365 fail();
366 } catch (MalformedTemplateNameException e) {
367 // expected
368 }
369 try {
370 cfg.getTemplate("\\x");
371 fail();
372 } catch (MalformedTemplateNameException e) {
373 // expected
374 }
375 }
376
377 }