Skip to content

Commit fcfb2d1

Browse files
committed
sync(2.0.x): backport 3.0.x optimizations (JDK 17 adaptation)
Port the non-dependency 3.0.x improvements to the 2.0.x line (JDK 17): - Security: OGNL DefaultMemberAccess(false,false,false), Zip Slip canonical path check, strict variable mode (-Deasydoc.variable.strict=true) - Correctness: font-mapper null-safe putIfAvailable, mergeDocx temp file deleted on stream close, try-with-resources IO cleanup (incl. ZipFolderHelper), mutable static cleanup (WMLPackageUtils.FACTORY final, FontMapperHolder volatile) - Architecture: AbstractWmlTemplate skeleton + VariableReplacer SPI (sealed interface + record strategies, JDK 17) collapsing the 3 docx templates; EngineFactory + Renderer separation across 8 engine modules; beetl placeholder defaults aligned with test properties; httl/rythm/webit @deprecated; XHTMLDocumentHandler file:/jar: URL support; JSP variables injected as request attributes (javax.servlet namespace preserved) - Build: javadoc plugin 3.11.2 -> 3.12.0 with native <doclint>none</doclint> Full reactor verify on JDK 17 (Corretto 17): BUILD SUCCESS, all modules green (core 338, xhtml 32, jsp 64, engines 8-20 each).
1 parent ed2f390 commit fcfb2d1

59 files changed

Lines changed: 3750 additions & 935 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2018, hiwepy (https://github.com/easy-4-java).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* 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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package io.github.easy4j.doc.beetl;
17+
18+
import java.io.IOException;
19+
import java.util.Map;
20+
21+
import org.beetl.core.Configuration;
22+
import org.beetl.core.GroupTemplate;
23+
import org.beetl.core.resource.ClasspathResourceLoader;
24+
import org.docx4j.Docx4jProperties;
25+
import io.github.easy4j.doc.Docx4jConstants;
26+
27+
/**
28+
* Immutable factory that lazily creates and caches a Beetl {@link GroupTemplate}
29+
* instance using double-checked locking.
30+
*
31+
* @author <a href="https://github.com/loong10k">Loong Wan</a>
32+
*/
33+
public final class EngineFactory {
34+
35+
private volatile GroupTemplate engine;
36+
37+
/**
38+
* Returns the shared {@link GroupTemplate} instance, creating it on first access.
39+
*/
40+
public GroupTemplate get() throws IOException {
41+
GroupTemplate local = engine;
42+
if (local == null) {
43+
synchronized (this) {
44+
local = engine;
45+
if (local == null) {
46+
ClasspathResourceLoader loader = new ClasspathResourceLoader();
47+
Configuration cfg = Configuration.defaultConfiguration();
48+
cfg.setCharset(Docx4jProperties.getProperty("docx4j.beetl.charset", Docx4jConstants.DEFAULT_CHARSETNAME));
49+
cfg.setPlaceholderStart(Docx4jProperties.getProperty("docx4j.beetl.placeholderStart", "${"));
50+
cfg.setPlaceholderEnd(Docx4jProperties.getProperty("docx4j.beetl.placeholderEnd", "}"));
51+
cfg.setStatementStart(Docx4jProperties.getProperty("docx4j.beetl.statementStart", "<%"));
52+
cfg.setStatementEnd(Docx4jProperties.getProperty("docx4j.beetl.statementEnd", "%>"));
53+
cfg.setHtmlTagSupport(Docx4jProperties.getProperty("docx4j.beetl.htmlTagSupport", false));
54+
cfg.setHtmlTagFlag(Docx4jProperties.getProperty("docx4j.beetl.htmlTagFlag", "#"));
55+
cfg.setHtmlTagBindingAttribute(Docx4jProperties.getProperty("docx4j.beetl.htmlTagBindingAttribute", "var"));
56+
cfg.setNativeCall(Docx4jProperties.getProperty("docx4j.beetl.nativeCall", false));
57+
cfg.setDirectByteOutput(Docx4jProperties.getProperty("docx4j.beetl.directByteOutput", true));
58+
cfg.setStrict(Docx4jProperties.getProperty("docx4j.beetl.strict", false));
59+
cfg.setIgnoreClientIOError(Docx4jProperties.getProperty("docx4j.beetl.ignoreClientIOError", true));
60+
cfg.setErrorHandlerClass(Docx4jProperties.getProperty("docx4j.beetl.errorHandlerClass", "org.beetl.core.ConsoleErrorHandler"));
61+
62+
Map<String, String> resourceMap = cfg.getResourceMap();
63+
resourceMap.put("root", Docx4jProperties.getProperty("docx4j.beetl.resource.root", "/"));
64+
resourceMap.put("autoCheck", Docx4jProperties.getProperty("docx4j.beetl.resource.autoCheck", "true"));
65+
resourceMap.put("functionRoot", Docx4jProperties.getProperty("docx4j.beetl.resource.functionRoot", "functions"));
66+
resourceMap.put("functionSuffix", Docx4jProperties.getProperty("docx4j.beetl.resource.functionSuffix", "html"));
67+
resourceMap.put("tagRoot", Docx4jProperties.getProperty("docx4j.beetl.resource.tagRoot", "htmltag"));
68+
resourceMap.put("tagSuffix", Docx4jProperties.getProperty("docx4j.beetl.resource.tagSuffix", "tag"));
69+
cfg.setResourceMap(resourceMap);
70+
local = new GroupTemplate(loader, cfg);
71+
engine = local;
72+
}
73+
}
74+
}
75+
return local;
76+
}
77+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2018, hiwepy (https://github.com/easy-4-java).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* 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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package io.github.easy4j.doc.beetl;
17+
18+
import java.util.Map;
19+
20+
import org.beetl.core.GroupTemplate;
21+
import org.beetl.core.Template;
22+
23+
/**
24+
* Stateless renderer that delegates to the Beetl {@link GroupTemplate} to
25+
* produce output from a named template and a set of variables.
26+
*
27+
* @author <a href="https://github.com/loong10k">Loong Wan</a>
28+
*/
29+
public final class Renderer {
30+
31+
/**
32+
* Renders the given template using the supplied engine and variables.
33+
*
34+
* @param template the template name / path
35+
* @param variables the template variables
36+
* @param engine the Beetl engine to use
37+
* @return the rendered output
38+
*/
39+
public String render(String template, Map<String, Object> variables, GroupTemplate engine) throws Exception {
40+
Template beeTemplate = engine.getTemplate(template);
41+
beeTemplate.binding(variables);
42+
return beeTemplate.render();
43+
}
44+
}

easydoc-beetl/src/main/java/io/github/easy4j/doc/beetl/WordprocessingMLBeetlTemplate.java

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,8 @@
2323

2424
import org.apache.commons.io.FileUtils;
2525
import org.apache.commons.io.IOUtils;
26-
import org.beetl.core.Configuration;
2726
import org.beetl.core.GroupTemplate;
28-
import org.beetl.core.Template;
29-
import org.beetl.core.resource.ClasspathResourceLoader;
30-
import org.docx4j.Docx4jProperties;
3127
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
32-
import io.github.easy4j.doc.Docx4jConstants;
3328
import io.github.easy4j.doc.WordprocessingMLTemplate;
3429
import io.github.easy4j.doc.xhtml.WordprocessingMLHtmlTemplate;
3530

@@ -39,18 +34,20 @@
3934
* @author <a href="https://github.com/loong10k">Loong Wan</a>
4035
*/
4136
public class WordprocessingMLBeetlTemplate implements WordprocessingMLTemplate {
42-
43-
protected GroupTemplate engine;
37+
38+
private final EngineFactory factory = new EngineFactory();
39+
private final Renderer renderer = new Renderer();
40+
protected volatile GroupTemplate engine;
4441
protected WordprocessingMLHtmlTemplate mlHtmlTemplate;
4542

4643
public WordprocessingMLBeetlTemplate() {
4744
this(false, false);
4845
}
49-
46+
5047
public WordprocessingMLBeetlTemplate(boolean landscape, boolean altChunk) {
51-
this.mlHtmlTemplate = new WordprocessingMLHtmlTemplate(landscape, altChunk) ;
48+
this.mlHtmlTemplate = new WordprocessingMLHtmlTemplate(landscape, altChunk);
5249
}
53-
50+
5451
public WordprocessingMLBeetlTemplate(WordprocessingMLHtmlTemplate template) {
5552
this.mlHtmlTemplate = template;
5653
}
@@ -59,7 +56,7 @@ public WordprocessingMLBeetlTemplate(WordprocessingMLHtmlTemplate template) {
5956
public WordprocessingMLPackage process(File template, Map<String, Object> variables) throws Exception {
6057
return this.process(FileUtils.readFileToString(template, StandardCharsets.UTF_8), variables);
6158
}
62-
59+
6360
@Override
6461
public WordprocessingMLPackage process(InputStream template, Map<String, Object> variables) throws Exception {
6562
return this.process(IOUtils.toString(template, StandardCharsets.UTF_8), variables);
@@ -74,12 +71,7 @@ public WordprocessingMLPackage process(InputStream template, Map<String, Object>
7471
*/
7572
@Override
7673
public WordprocessingMLPackage process(String template, Map<String, Object> variables) throws Exception {
77-
//使用Beetl模板引擎渲染模板
78-
Template beeTemplate = getEngine().getTemplate(template);
79-
beeTemplate.binding(variables);
80-
//获取模板渲染后的结果
81-
String html = beeTemplate.render();
82-
//使用HtmlTemplate进行渲染
74+
String html = renderer.render(template, variables, getEngine());
8375
return mlHtmlTemplate.process(html, variables);
8476
}
8577

@@ -90,57 +82,9 @@ public GroupTemplate getEngine() throws IOException {
9082
public void setEngine(GroupTemplate engine) {
9183
this.engine = engine;
9284
}
93-
94-
protected synchronized GroupTemplate getInternalEngine() throws IOException{
95-
ClasspathResourceLoader loader = new ClasspathResourceLoader();
96-
//加载默认参数
97-
Configuration cfg = Configuration.defaultConfiguration();
98-
//模板字符集
99-
cfg.setCharset(Docx4jProperties.getProperty("docx4j.beetl.charset", Docx4jConstants.DEFAULT_CHARSETNAME));
100-
//模板占位起始符号
101-
cfg.setPlaceholderStart(Docx4jProperties.getProperty("docx4j.beetl.placeholderStart", "${"));
102-
//模板占位结束符号
103-
cfg.setPlaceholderEnd(Docx4jProperties.getProperty("docx4j.beetl.placeholderEnd", "<%"));
104-
//控制语句起始符号
105-
cfg.setStatementStart(Docx4jProperties.getProperty("docx4j.beetl.statementStart", "%>"));
106-
//控制语句结束符号
107-
cfg.setStatementEnd(Docx4jProperties.getProperty("docx4j.beetl.statementEnd", "}"));
108-
//是否允许html tag,在web编程中,有可能用到html tag,最好允许
109-
cfg.setHtmlTagSupport(Docx4jProperties.getProperty("docx4j.beetl.htmlTagSupport", false));
110-
//html tag 标示符号
111-
cfg.setHtmlTagFlag(Docx4jProperties.getProperty("docx4j.beetl.htmlTagFlag", "#"));
112-
//html 绑定的属性,如&lt;aa var="customer">
113-
cfg.setHtmlTagBindingAttribute(Docx4jProperties.getProperty("docx4j.beetl.htmlTagBindingAttribute", "var"));
114-
//是否允许直接调用class
115-
cfg.setNativeCall(Docx4jProperties.getProperty("docx4j.beetl.nativeCall", false));
116-
//输出模式,默认是字符集输出,改成byte输出提高性能
117-
cfg.setDirectByteOutput(Docx4jProperties.getProperty("docx4j.beetl.directByteOutput", true));
118-
//严格mvc应用,只有变态的的人才打开此选项
119-
cfg.setStrict(Docx4jProperties.getProperty("docx4j.beetl.strict", false));
120-
//是否忽略客户端的网络异常
121-
cfg.setIgnoreClientIOError(Docx4jProperties.getProperty("docx4j.beetl.ignoreClientIOError", true));
122-
//错误处理类
123-
cfg.setErrorHandlerClass(Docx4jProperties.getProperty("docx4j.beetl.errorHandlerClass", "org.beetl.core.ConsoleErrorHandler"));
124-
125-
//资源参数
126-
Map<String,String> resourceMap = cfg.getResourceMap();
127-
//classpath 跟路径
128-
resourceMap.put("root", Docx4jProperties.getProperty("docx4j.beetl.resource.root", "/"));
129-
//是否检测文件变化
130-
resourceMap.put("autoCheck", Docx4jProperties.getProperty("docx4j.beetl.resource.autoCheck", "true"));
131-
//自定义脚本方法文件位置
132-
resourceMap.put("functionRoot", Docx4jProperties.getProperty("docx4j.beetl.resource.functionRoot", "functions"));
133-
//自定义脚本方法文件的后缀
134-
resourceMap.put("functionSuffix", Docx4jProperties.getProperty("docx4j.beetl.resource.functionSuffix", "html"));
135-
//自定义标签文件位置
136-
resourceMap.put("tagRoot", Docx4jProperties.getProperty("docx4j.beetl.resource.tagRoot", "htmltag"));
137-
//自定义标签文件后缀
138-
resourceMap.put("tagSuffix", Docx4jProperties.getProperty("docx4j.beetl.resource.tagSuffix", "tag"));
139-
cfg.setResourceMap(resourceMap);
140-
GroupTemplate engine = new GroupTemplate(loader, cfg);
141-
// 设置模板引擎,减少重复初始化消耗
142-
this.setEngine(engine);
143-
return engine;
85+
86+
protected GroupTemplate getInternalEngine() throws IOException {
87+
return factory.get();
14488
}
14589

14690
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.github.easy4j.doc.beetl;
2+
3+
import static org.junit.jupiter.api.Assertions.assertNotNull;
4+
import static org.junit.jupiter.api.Assertions.assertSame;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.lang.reflect.Field;
8+
import java.lang.reflect.Modifier;
9+
10+
import org.beetl.core.GroupTemplate;
11+
import org.junit.jupiter.api.DisplayName;
12+
import org.junit.jupiter.api.Test;
13+
14+
/**
15+
* Coverage tests for EngineFactory DCL short-circuit and Renderer statelessness.
16+
*
17+
* @author <a href="https://github.com/loong10k">Loong Wan</a>
18+
*/
19+
@DisplayName("WordprocessingMLBeetlTemplate Coverage Tests")
20+
class WordprocessingMLBeetlCoverageTest {
21+
22+
/**
23+
* Exercises the EngineFactory DCL short-circuit: consecutive calls to
24+
* {@code factory.get()} must return the same instance without re-entering
25+
* the synchronized block.
26+
*/
27+
@Test
28+
@DisplayName("EngineFactory.get() returns same cached instance on consecutive calls")
29+
void engineFactoryReturnsSameInstanceOnConsecutiveCalls() throws Exception {
30+
EngineFactory factory = new EngineFactory();
31+
GroupTemplate first = factory.get();
32+
assertNotNull(first);
33+
GroupTemplate second = factory.get();
34+
assertSame(first, second, "EngineFactory.get() must return the same cached instance");
35+
}
36+
37+
/**
38+
* Verifies that Renderer is stateless: it has no mutable instance fields.
39+
*/
40+
@Test
41+
@DisplayName("Renderer has no mutable instance fields (stateless)")
42+
void rendererIsStateless() {
43+
Field[] fields = Renderer.class.getDeclaredFields();
44+
for (Field f : fields) {
45+
if (!Modifier.isStatic(f.getModifiers())) {
46+
assertTrue(Modifier.isFinal(f.getModifiers()),
47+
"Renderer field '" + f.getName() + "' must be final to guarantee statelessness");
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)