1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.devacfr.maven.skins.reflow.snippet;
20
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23
24 import java.io.StringWriter;
25 import java.io.Writer;
26 import java.lang.ref.WeakReference;
27 import java.util.List;
28 import java.util.UUID;
29 import java.util.function.Consumer;
30
31 import com.google.common.collect.Lists;
32 import org.apache.velocity.app.Velocity;
33 import org.apache.velocity.context.Context;
34 import org.apache.velocity.runtime.RuntimeConstants;
35 import org.apache.velocity.runtime.RuntimeSingleton;
36 import org.apache.velocity.tools.Scope;
37 import org.apache.velocity.tools.ToolManager;
38 import org.apache.velocity.tools.config.EasyFactoryConfiguration;
39 import org.apache.velocity.tools.generic.ClassTool;
40 import org.apache.velocity.tools.generic.ComparisonDateTool;
41 import org.apache.velocity.tools.generic.ContextTool;
42 import org.apache.velocity.tools.generic.DisplayTool;
43 import org.apache.velocity.tools.generic.EscapeTool;
44 import org.apache.velocity.tools.generic.FieldTool;
45 import org.apache.velocity.tools.generic.LinkTool;
46 import org.apache.velocity.tools.generic.LoopTool;
47 import org.apache.velocity.tools.generic.MathTool;
48 import org.apache.velocity.tools.generic.NumberTool;
49 import org.apache.velocity.tools.generic.RenderTool;
50 import org.apache.velocity.tools.generic.ResourceTool;
51 import org.apache.velocity.tools.generic.XmlTool;
52 import org.devacfr.maven.skins.reflow.HtmlTool;
53 import org.devacfr.maven.skins.reflow.ISkinConfig;
54 import org.devacfr.maven.skins.reflow.URITool;
55 import org.devacfr.maven.skins.reflow.snippet.ComponentToken.Type;
56 import org.jsoup.Jsoup;
57 import org.jsoup.nodes.Document;
58 import org.jsoup.nodes.Element;
59 import org.jsoup.nodes.Node;
60 import org.jsoup.nodes.TextNode;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 import static java.util.Objects.requireNonNull;
65
66
67
68
69
70 public class SnippetContext {
71
72
73 private static final Logger LOGGER = LoggerFactory.getLogger(SnippetContext.class);
74
75
76 private static final List<String> DEFAULT_PATHS = Lists.newArrayList("src/site/layouts/snippets",
77 "META-INF/skin/snippets");
78
79
80 private final List<SnippetComponent<?>> components = Lists.newArrayList();
81
82
83 private String htmlSource;
84
85
86 private final List<String> snippetPaths = Lists.newArrayList(DEFAULT_PATHS);
87
88
89 private ISkinConfig config;
90
91
92 private final ToolManager toolManager;
93
94
95 private final WeakReference<SnippetParser> parser;
96
97 public SnippetContext(final SnippetParser parser) {
98 this.toolManager = createToolManaged();
99 this.parser = new WeakReference<>(parser);
100 }
101
102 public void reset() {
103 this.htmlSource = null;
104 this.components.clear();
105 this.config = null;
106 }
107
108 public String generateSnippetIdentifier() {
109 return "snippet-placement-" + UUID.randomUUID().toString();
110 }
111
112 public List<SnippetComponent<?>> getComponents() {
113 return components;
114 }
115
116 public SnippetParser getParser() {
117 return this.parser.get();
118 }
119
120 public ISkinConfig getConfig() {
121 return config;
122 }
123
124 public SnippetParser createChildParser() {
125 final SnippetParser parser = new SnippetParser();
126 getSnippetPaths().forEach(path -> parser.addResourcePath(path));
127 return parser;
128 }
129
130 void setConfig(final ISkinConfig config) {
131 this.config = config;
132 }
133
134 public List<String> getSnippetPaths() {
135 return snippetPaths;
136 }
137
138 void addComponent(final SnippetComponent<?> component) {
139 this.components.add(component);
140 }
141
142 void insertResourcePath(final String path, final int index) {
143 this.snippetPaths.add(index, path);
144 }
145
146 void addResourcePath(final String path) {
147 this.snippetPaths.add(path);
148 }
149
150 void setHtmlSource(final String htmlSource) {
151 this.htmlSource = htmlSource;
152 }
153
154 public String html() {
155 return htmlSource;
156 }
157
158 public Document document() {
159 return Jsoup.parse(html());
160 }
161
162 @Nonnull
163 SnippetComponent<?> create(@Nonnull final Element element,
164 @Nonnull final ComponentToken startToken,
165 @Nullable final ComponentToken endToken) {
166 requireNonNull(element);
167 requireNonNull(startToken);
168 final SnippetComponent<?> component = SnippetComponent.createSnippet(element, null, startToken.type());
169 addComponent(component);
170 recurciveCreateComponent(element, component);
171 return component;
172 }
173
174 @Nonnull
175 private SnippetComponent<?> create(@Nonnull final Element element, final Component<?> commponent) {
176 requireNonNull(element);
177 Type type = null;
178 if (element.hasAttr("shortcode")) {
179 type = Type.shortcode;
180 } else if (element.hasAttr("webcomponent")) {
181 type = Type.webComponent;
182 } else {
183 throw new SnippetParseException("Unknown snippet element");
184 }
185 final SnippetComponent<?> component = SnippetComponent.createSnippet(element, commponent, type);
186 addComponent(component);
187 recurciveCreateComponent(element, component);
188 return component;
189 }
190
191 private void recurciveCreateComponent(@Nonnull final Node element, final Component<?> parent) {
192 element.childNodes().forEach(child -> {
193 Component<?> component = null;
194
195 if (child instanceof TextNode && ((TextNode) child).text().length() > 1) {
196 component = Component.createComponent(child, parent);
197 } else if (child instanceof Element) {
198 final Element el = (Element) child;
199 if (ComponentResolver.isSnippet(el)) {
200 component = create(el, parent);
201 } else {
202 component = Component.createComponent(el, parent);
203 recurciveCreateComponent(el, component);
204 }
205 }
206 if (component != null) {
207 parent.addChild(component);
208 }
209 });
210 }
211
212 protected void render(final SnippetComponent<?> component) {
213 traverseTee(component, c -> {
214 if (c instanceof SnippetComponent) {
215 ((SnippetComponent<?>) c).render(this);
216 }
217 });
218 component.render(this);
219 }
220
221 private void traverseTee(final Component<?> component, final Consumer<Component<?>> consumer) {
222 final Consumer<Component<?>> traverse = c -> traverseTee(c, consumer);
223 component.getChildren().forEach(consumer.andThen(traverse));
224 }
225
226 protected String renderComponent(final SnippetComponent<?> component) {
227 final StringWriter writer = new StringWriter();
228 mergeTemplate(component, writer);
229 return writer.toString();
230 }
231
232 protected void mergeTemplate(final SnippetComponent<?> component, final Writer writer) {
233 boolean found = false;
234 for (final String path : this.snippetPaths) {
235 final String filePath = path + '/' + component.getName() + ".vm";
236 if (Velocity.resourceExists(filePath)) {
237 found = true;
238 final Context context = createVelocityContext();
239 context.put("snippet", component);
240 context.put("snippetPath", filePath);
241 context.put("config", this.config);
242 context.put("velocity", Velocity.class);
243 context.put("site", this.config.getSiteModel());
244
245 context.put("resourcePath", this.config.getResourcePath());
246
247 Velocity.mergeTemplate("META-INF/skin/snippets/_snippet.vm",
248 RuntimeSingleton.getString(RuntimeConstants.INPUT_ENCODING, RuntimeConstants.ENCODING_DEFAULT),
249 context,
250 writer);
251 break;
252 } else {
253 if (LOGGER.isDebugEnabled()) {
254 LOGGER.debug("Template for component '{}' not found in path:{} ", component, filePath);
255 }
256 }
257 }
258 if (!found) {
259 LOGGER.warn("The snippet '{}' template doesn't exist", component.getName());
260 }
261 }
262
263
264
265
266
267
268 protected Context createVelocityContext() {
269 return toolManager.createContext();
270 }
271
272
273
274
275 protected ToolManager createToolManaged() {
276
277 final EasyFactoryConfiguration config = new EasyFactoryConfiguration(false);
278 config.property("safeMode", Boolean.FALSE);
279 config.toolbox(Scope.REQUEST)
280 .tool(ContextTool.class)
281 .tool(LinkTool.class)
282 .tool(LoopTool.class)
283 .tool(RenderTool.class);
284 config.toolbox(Scope.APPLICATION)
285 .tool(ClassTool.class)
286 .tool(ComparisonDateTool.class)
287 .tool(DisplayTool.class)
288 .tool(EscapeTool.class)
289 .tool(FieldTool.class)
290 .tool(MathTool.class)
291 .tool(NumberTool.class)
292 .tool(ResourceTool.class)
293 .tool(XmlTool.class)
294 .tool(URITool.class)
295 .tool(HtmlTool.class);
296
297 final ToolManager manager = new ToolManager(false, false);
298 manager.configure(config);
299 return manager;
300 }
301 }