1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.esigate.extension.parallelesi;
17
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.concurrent.Callable;
25 import java.util.concurrent.Executor;
26 import java.util.concurrent.Future;
27 import java.util.concurrent.FutureTask;
28 import java.util.concurrent.RejectedExecutionException;
29 import java.util.concurrent.RunnableFuture;
30 import java.util.regex.Pattern;
31
32 import org.apache.commons.io.output.StringBuilderWriter;
33 import org.apache.http.client.methods.CloseableHttpResponse;
34 import org.esigate.ConfigurationException;
35 import org.esigate.Driver;
36 import org.esigate.DriverFactory;
37 import org.esigate.HttpErrorPage;
38 import org.esigate.Parameters;
39 import org.esigate.Renderer;
40 import org.esigate.http.HttpResponseUtils;
41 import org.esigate.impl.DriverRequest;
42 import org.esigate.parser.future.CharSequenceFuture;
43 import org.esigate.parser.future.FutureElement;
44 import org.esigate.parser.future.FutureElementType;
45 import org.esigate.parser.future.FutureParserContext;
46 import org.esigate.parser.future.StringBuilderFutureAppendable;
47 import org.esigate.xml.XpathRenderer;
48 import org.esigate.xml.XsltRenderer;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 class IncludeElement extends BaseElement {
53 private static final String PROVIDER_PATTERN = "$(PROVIDER{";
54 private static final String LEGACY_PROVIDER_PATTERN = "$PROVIDER({";
55
56 private static final Logger LOG = LoggerFactory.getLogger(IncludeElement.class);
57
58 private static final class IncludeTask implements Callable<CharSequence> {
59 private String src;
60 private String alt;
61 private FutureParserContext ctx;
62 private boolean ignoreError;
63 private FutureElement current;
64 private Tag includeTag;
65 private Map<String, CharSequence> fragmentReplacements;
66 private Map<String, CharSequence> regexpReplacements;
67 private Executor executor;
68
69 private IncludeTask(Tag includeTag, String src, String alt, FutureParserContext ctx, FutureElement current,
70 boolean ignoreError, Map<String, CharSequence> fragmentReplacements,
71 Map<String, CharSequence> regexpReplacements, Executor executor) {
72 this.src = src;
73 this.alt = alt;
74 this.ctx = ctx;
75 this.ignoreError = ignoreError;
76 this.current = current;
77 this.includeTag = includeTag;
78 this.fragmentReplacements = fragmentReplacements;
79 this.regexpReplacements = regexpReplacements;
80 this.executor = executor;
81 }
82
83 @Override
84 public CharSequence call() throws IOException, HttpErrorPage {
85 LOG.debug("Starting include task {}", this.src);
86 StringBuilderWriter sw = new StringBuilderWriter(Parameters.DEFAULT_BUFFER_SIZE);
87
88 Exception currentException = null;
89
90 try {
91 processPage(this.src, includeTag, sw);
92 } catch (IOException | HttpErrorPage e) {
93 currentException = e;
94 } catch (ConfigurationException e) {
95
96 currentException = e;
97 LOG.error("Esi Include Tag with unknown Provider :" + e.getMessage());
98 }
99
100
101 if (currentException != null && alt != null) {
102
103 currentException = null;
104 try {
105 processPage(alt, includeTag, sw);
106 } catch (IOException | HttpErrorPage e) {
107 currentException = e;
108 } catch (ConfigurationException e) {
109
110 currentException = e;
111 LOG.error("Esi Include Tag with unknown Provider :" + e.getMessage());
112 }
113 }
114
115
116 if (currentException != null && !ignoreError && !ctx.reportError(current, currentException)) {
117 if (currentException instanceof IOException) {
118 throw (IOException) currentException;
119 } else if (currentException instanceof HttpErrorPage) {
120 throw (HttpErrorPage) currentException;
121 } else if (currentException instanceof ConfigurationException) {
122 throw (ConfigurationException) currentException;
123 }
124 throw new IllegalStateException(
125 "This type of exception is unexpected here. Should be IOException or HttpErrorPageException or ConfigurationException.",
126 currentException);
127 }
128
129
130 String result = sw.toString();
131
132 if (!regexpReplacements.isEmpty()) {
133 for (Entry<String, CharSequence> entry : regexpReplacements.entrySet()) {
134
135 result = Pattern.compile(entry.getKey()).matcher(result).replaceAll(entry.getValue().toString());
136 }
137 }
138
139 return result;
140 }
141
142 private void processPage(String srcOrAlt, Tag tag, Appendable out) throws IOException, HttpErrorPage {
143 String fragment = tag.getAttribute("fragment");
144 String xpath = tag.getAttribute("xpath");
145 String xslt = tag.getAttribute("stylesheet");
146
147 DriverRequest httpRequest = ctx.getHttpRequest();
148 List<Renderer> rendererList = new ArrayList<>();
149 Driver driver;
150 String page;
151
152 int idx = srcOrAlt.indexOf(PROVIDER_PATTERN);
153 int idxLegacyPattern = srcOrAlt.indexOf(LEGACY_PROVIDER_PATTERN);
154 if (idx < 0 && idxLegacyPattern < 0) {
155 page = srcOrAlt;
156 driver = httpRequest.getDriver();
157 } else if (idx >= 0) {
158 int startIdx = idx + PROVIDER_PATTERN.length();
159 int endIndex = srcOrAlt.indexOf("})", startIdx);
160 String provider = srcOrAlt.substring(startIdx, endIndex);
161 page = srcOrAlt.substring(endIndex + "})".length());
162 driver = DriverFactory.getInstance(provider);
163 if (LOG.isWarnEnabled() && idx > 0) {
164 LOG.warn("Invalid src attribute : [{}], src should start with [{}{}})]."
165 + " First characters [{}] have been ignored", srcOrAlt, PROVIDER_PATTERN, provider,
166 srcOrAlt.substring(0, idx));
167 }
168 } else {
169 int startIdx = idxLegacyPattern + PROVIDER_PATTERN.length();
170 int endIndex = srcOrAlt.indexOf("})", startIdx);
171 String provider = srcOrAlt.substring(startIdx, endIndex);
172 page = srcOrAlt.substring(endIndex + "})".length());
173 driver = DriverFactory.getInstance(provider);
174 if (LOG.isWarnEnabled() && idxLegacyPattern > 0) {
175 LOG.warn("Invalid src attribute : [{}], src should start with [{}{}})]."
176 + " First characters [{}] have been ignored", srcOrAlt, PROVIDER_PATTERN, provider,
177 srcOrAlt.substring(0, idxLegacyPattern));
178 }
179 }
180
181 InlineCache ic = InlineCache.getFragment(srcOrAlt);
182 if (ic != null && !ic.isExpired()) {
183 String cache = ic.getFragment();
184 out.append(cache);
185 } else {
186 EsiRenderer esiRenderer;
187 if (fragment != null) {
188 esiRenderer = new EsiRenderer(page, fragment, executor);
189 } else {
190 esiRenderer = new EsiRenderer(executor);
191 }
192 if (fragmentReplacements != null && !fragmentReplacements.isEmpty()) {
193 esiRenderer.setFragmentsToReplace(fragmentReplacements);
194 }
195 rendererList.add(esiRenderer);
196 if (xpath != null) {
197 rendererList.add(new XpathRenderer(xpath));
198 } else if (xslt != null) {
199 rendererList.add(new XsltRenderer(xslt, driver, httpRequest));
200 }
201 CloseableHttpResponse response =
202 driver.render(page, httpRequest.getOriginalRequest(),
203 rendererList.toArray(new Renderer[rendererList.size()]));
204 out.append(HttpResponseUtils.toString(response));
205 }
206 }
207
208 }
209
210 public static final FutureElementType TYPE = new BaseElementType("<esi:include", "</esi:include") {
211 @Override
212 public IncludeElement newInstance() {
213 return new IncludeElement();
214 }
215
216 };
217
218 private StringBuilderFutureAppendable buf;
219 private Map<String, CharSequence> fragmentReplacements;
220 private Map<String, CharSequence> regexpReplacements;
221 private Tag includeTag;
222 private boolean write = false;
223
224 IncludeElement() {
225 }
226
227 @Override
228 public void characters(Future<CharSequence> csq) {
229 if (write) {
230 buf.enqueueAppend(csq);
231 }
232 }
233
234 @Override
235 public void onTagEnd(String tag, FutureParserContext ctx) throws IOException, HttpErrorPage {
236 write = true;
237 String src = includeTag.getAttribute("src");
238 String alt = includeTag.getAttribute("alt");
239 boolean ignoreError = "continue".equals(includeTag.getAttribute("onerror"));
240 FutureElement current = ctx.getCurrent();
241
242 Executor executor = (Executor) ctx.getData(EsiRenderer.DATA_EXECUTOR);
243 Future<CharSequence> result;
244 IncludeTask task =
245 new IncludeTask(includeTag, src, alt, ctx, current, ignoreError, fragmentReplacements,
246 regexpReplacements, null);
247 if (executor == null) {
248
249 CharSequence content = task.call();
250 result = new CharSequenceFuture(content);
251 } else {
252
253 try {
254 RunnableFuture<CharSequence> r = new FutureTask<>(task);
255 executor.execute(r);
256 result = r;
257 } catch (RejectedExecutionException e) {
258 throw new HttpErrorPage(509, "Limits exceeded", e);
259 }
260 }
261 ctx.getCurrent().characters(result);
262 }
263
264 @Override
265 protected boolean parseTag(Tag tag, FutureParserContext ctx) {
266 buf = new StringBuilderFutureAppendable();
267 fragmentReplacements = new HashMap<>();
268 regexpReplacements = new HashMap<>();
269 includeTag = tag;
270 return true;
271 }
272
273 void addFragmentReplacement(String fragment, CharSequence replacement) {
274 fragmentReplacements.put(fragment, replacement);
275 }
276
277 void addRegexpReplacement(String regexp, CharSequence replacement) {
278 regexpReplacements.put(regexp, replacement);
279 }
280
281 }