Class |
Line # |
Actions |
|||||
---|---|---|---|---|---|---|---|
SnippetContext | 70 | 84 | 0% | 35 | 16 |
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 | 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 | * @author Christophe Friederich | |
68 | * @version 2.4 | |
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 | 7 | public SnippetContext(final SnippetParser parser) { |
98 | 7 | this.toolManager = createToolManaged(); |
99 | 7 | this.parser = new WeakReference<>(parser); |
100 | } | |
101 | ||
102 | 7 | public void reset() { |
103 | 7 | this.htmlSource = null; |
104 | 7 | this.components.clear(); |
105 | 7 | this.config = null; |
106 | } | |
107 | ||
108 | 0 | public String generateSnippetIdentifier() { |
109 | 0 | return "snippet-placement-" + UUID.randomUUID().toString(); |
110 | } | |
111 | ||
112 | 0 | public List<SnippetComponent<?>> getComponents() { |
113 | 0 | return components; |
114 | } | |
115 | ||
116 | 0 | public SnippetParser getParser() { |
117 | 0 | return this.parser.get(); |
118 | } | |
119 | ||
120 | 1 | public ISkinConfig getConfig() { |
121 | 1 | return config; |
122 | } | |
123 | ||
124 | 1 | public SnippetParser createChildParser() { |
125 | 1 | final SnippetParser parser = new SnippetParser(); |
126 | 1 | getSnippetPaths().forEach(path -> parser.addResourcePath(path)); |
127 | 1 | return parser; |
128 | } | |
129 | ||
130 | 7 | void setConfig(final ISkinConfig config) { |
131 | 7 | this.config = config; |
132 | } | |
133 | ||
134 | 1 | public List<String> getSnippetPaths() { |
135 | 1 | return snippetPaths; |
136 | } | |
137 | ||
138 | 31 | void addComponent(final SnippetComponent<?> component) { |
139 | 31 | this.components.add(component); |
140 | } | |
141 | ||
142 | 6 | void insertResourcePath(final String path, final int index) { |
143 | 6 | this.snippetPaths.add(index, path); |
144 | } | |
145 | ||
146 | 3 | void addResourcePath(final String path) { |
147 | 3 | this.snippetPaths.add(path); |
148 | } | |
149 | ||
150 | 7 | void setHtmlSource(final String htmlSource) { |
151 | 7 | this.htmlSource = htmlSource; |
152 | } | |
153 | ||
154 | 7 | public String html() { |
155 | 7 | return htmlSource; |
156 | } | |
157 | ||
158 | 1 | public Document document() { |
159 | 1 | return Jsoup.parse(html()); |
160 | } | |
161 | ||
162 | 29 | @Nonnull |
163 | SnippetComponent<?> create(@Nonnull final Element element, | |
164 | @Nonnull final ComponentToken startToken, | |
165 | @Nullable final ComponentToken endToken) { | |
166 | 29 | requireNonNull(element); |
167 | 29 | requireNonNull(startToken); |
168 | 29 | final SnippetComponent<?> component = SnippetComponent.createSnippet(element, null, startToken.type()); |
169 | 29 | addComponent(component); |
170 | 29 | recurciveCreateComponent(element, component); |
171 | 29 | return component; |
172 | } | |
173 | ||
174 | 2 | @Nonnull |
175 | private SnippetComponent<?> create(@Nonnull final Element element, final Component<?> commponent) { | |
176 | 2 | requireNonNull(element); |
177 | 2 | Type type = null; |
178 | 2 | if (element.hasAttr("shortcode")) { |
179 | 2 | type = Type.shortcode; |
180 | 0 | } else if (element.hasAttr("webcomponent")) { |
181 | 0 | type = Type.webComponent; |
182 | } else { | |
183 | 0 | throw new SnippetParseException("Unknown snippet element"); |
184 | } | |
185 | 2 | final SnippetComponent<?> component = SnippetComponent.createSnippet(element, commponent, type); |
186 | 2 | addComponent(component); |
187 | 2 | recurciveCreateComponent(element, component); |
188 | 2 | return component; |
189 | } | |
190 | ||
191 | 193 | private void recurciveCreateComponent(@Nonnull final Node element, final Component<?> parent) { |
192 | 193 | element.childNodes().forEach(child -> { |
193 | 442 | Component<?> component = null; |
194 | // accept textnode not empty as component. | |
195 | 442 | if (child instanceof TextNode && ((TextNode) child).text().length() > 1) { |
196 | 90 | component = Component.createComponent(child, parent); |
197 | 352 | } else if (child instanceof Element) { |
198 | 164 | final Element el = (Element) child; |
199 | 164 | if (ComponentResolver.isSnippet(el)) { |
200 | 2 | component = create(el, parent); |
201 | } else { | |
202 | 162 | component = Component.createComponent(el, parent); |
203 | 162 | recurciveCreateComponent(el, component); |
204 | } | |
205 | } | |
206 | 442 | if (component != null) { |
207 | 254 | parent.addChild(component); |
208 | } | |
209 | }); | |
210 | } | |
211 | ||
212 | 29 | protected void render(final SnippetComponent<?> component) { |
213 | 29 | traverseTee(component, c -> { |
214 | 254 | if (c instanceof SnippetComponent) { |
215 | 2 | ((SnippetComponent<?>) c).render(this); |
216 | } | |
217 | }); | |
218 | 29 | component.render(this); |
219 | } | |
220 | ||
221 | 283 | private void traverseTee(final Component<?> component, final Consumer<Component<?>> consumer) { |
222 | 283 | final Consumer<Component<?>> traverse = c -> traverseTee(c, consumer); |
223 | 283 | component.getChildren().forEach(consumer.andThen(traverse)); |
224 | } | |
225 | ||
226 | 31 | protected String renderComponent(final SnippetComponent<?> component) { |
227 | 31 | final StringWriter writer = new StringWriter(); |
228 | 31 | mergeTemplate(component, writer); |
229 | 31 | return writer.toString(); |
230 | } | |
231 | ||
232 | 31 | protected void mergeTemplate(final SnippetComponent<?> component, final Writer writer) { |
233 | 31 | boolean found = false; |
234 | 31 | for (final String path : this.snippetPaths) { |
235 | 83 | final String filePath = path + '/' + component.getName() + ".vm"; |
236 | 83 | if (Velocity.resourceExists(filePath)) { |
237 | 31 | found = true; |
238 | 31 | final Context context = createVelocityContext(); |
239 | 31 | context.put("snippet", component); |
240 | 31 | context.put("snippetPath", filePath); |
241 | 31 | context.put("config", this.config); |
242 | 31 | context.put("velocity", Velocity.class); |
243 | 31 | context.put("site", this.config.getSiteModel()); |
244 | // Use config option <absoluteResourceURL>http://mysite.com/</absoluteResourceURL> | |
245 | 31 | context.put("resourcePath", this.config.getResourcePath()); |
246 | ||
247 | 31 | Velocity.mergeTemplate("META-INF/skin/snippets/_snippet.vm", |
248 | RuntimeSingleton.getString(RuntimeConstants.INPUT_ENCODING, RuntimeConstants.ENCODING_DEFAULT), | |
249 | context, | |
250 | writer); | |
251 | 31 | break; |
252 | } else { | |
253 | 52 | if (LOGGER.isDebugEnabled()) { |
254 | 0 | LOGGER.debug("Template for component '{}' not found in path:{} ", component, filePath); |
255 | } | |
256 | } | |
257 | } | |
258 | 31 | if (!found) { |
259 | 0 | LOGGER.warn("The snippet '{}' template doesn't exist", component.getName()); |
260 | } | |
261 | } | |
262 | ||
263 | /** | |
264 | * Creates a Velocity Context with all generic tools configured wit the site rendering context. | |
265 | * | |
266 | * @return a Velocity tools managed context | |
267 | */ | |
268 | 31 | protected Context createVelocityContext() { |
269 | 31 | return toolManager.createContext(); |
270 | } | |
271 | ||
272 | /** | |
273 | * @return | |
274 | */ | |
275 | 7 | protected ToolManager createToolManaged() { |
276 | ||
277 | 7 | final EasyFactoryConfiguration config = new EasyFactoryConfiguration(false); |
278 | 7 | config.property("safeMode", Boolean.FALSE); |
279 | 7 | config.toolbox(Scope.REQUEST) |
280 | .tool(ContextTool.class) | |
281 | .tool(LinkTool.class) | |
282 | .tool(LoopTool.class) | |
283 | .tool(RenderTool.class); | |
284 | 7 | 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 | 7 | final ToolManager manager = new ToolManager(false, false); |
298 | 7 | manager.configure(config); |
299 | 7 | return manager; |
300 | } | |
301 | } |