View Javadoc
1   /* 
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   *
14   */
15  
16  package org.esigate.esi;
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.regex.Pattern;
25  
26  import org.apache.http.client.methods.CloseableHttpResponse;
27  import org.esigate.ConfigurationException;
28  import org.esigate.Driver;
29  import org.esigate.DriverFactory;
30  import org.esigate.HttpErrorPage;
31  import org.esigate.Parameters;
32  import org.esigate.Renderer;
33  import org.esigate.http.HttpResponseUtils;
34  import org.esigate.impl.DriverRequest;
35  import org.esigate.parser.Adapter;
36  import org.esigate.parser.ElementType;
37  import org.esigate.parser.ParserContext;
38  import org.esigate.xml.XpathRenderer;
39  import org.esigate.xml.XsltRenderer;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  class IncludeElement extends BaseElement {
44      private static final String PROVIDER_PATTERN = "$(PROVIDER{";
45      private static final String LEGACY_PROVIDER_PATTERN = "$PROVIDER({";
46      private static final Logger LOG = LoggerFactory.getLogger(IncludeElement.class);
47      public static final ElementType TYPE = new BaseElementType("<esi:include", "</esi:include") {
48          @Override
49          public IncludeElement newInstance() {
50              return new IncludeElement();
51          }
52  
53      };
54  
55      private final Appendable outAdapter = new Adapter(IncludeElement.this);
56      private StringBuilder buf;
57      private Map<String, CharSequence> fragmentReplacements;
58      private Map<String, CharSequence> regexpReplacements;
59      private Tag includeTag;
60      private boolean write = false;
61  
62      IncludeElement() {
63      }
64  
65      @Override
66      public void characters(CharSequence csq, int start, int end) {
67          if (write) {
68              buf.append(csq, start, end);
69          }
70      }
71  
72      @Override
73      public void onTagEnd(String tag, ParserContext ctx) throws IOException, HttpErrorPage {
74          write = true;
75          String src = includeTag.getAttribute("src");
76          String alt = includeTag.getAttribute("alt");
77          boolean ignoreError = "continue".equals(includeTag.getAttribute("onerror"));
78  
79          Exception currentException = null;
80          // Handle src
81          try {
82              processPage(src, includeTag, ctx);
83          } catch (IOException | HttpErrorPage e) {
84              currentException = e;
85          } catch (ConfigurationException e) {
86              // case uknown provider : log error
87              currentException = e;
88              LOG.error("Esi Include Tag with unknown Provider :" + e.getMessage());
89          }
90  
91          // Handle Alt
92          if (currentException != null && alt != null) {
93              // Reset exception
94              currentException = null;
95              try {
96                  processPage(alt, includeTag, ctx);
97              } catch (IOException | HttpErrorPage e) {
98                  currentException = e;
99              } catch (ConfigurationException e) {
100                 // case uknown provider : log error
101                 currentException = e;
102                 LOG.error("Esi Include Tag with unknown Provider :" + e.getMessage());
103             }
104         }
105 
106         // Handle onerror
107         if (currentException != null && !ignoreError && !ctx.reportError(currentException)) {
108             if (currentException instanceof IOException) {
109                 throw (IOException) currentException;
110             } else if (currentException instanceof HttpErrorPage) {
111                 throw (HttpErrorPage) currentException;
112             } else if (currentException instanceof ConfigurationException) {
113                 throw (ConfigurationException) currentException;
114             }
115             throw new IllegalStateException(
116                     "This type of exception is unexpected here. Should be IOException or HttpErrorPageException or ConfigurationException.",
117                     currentException);
118 
119         }
120 
121         // apply regexp replacements
122         if (!regexpReplacements.isEmpty()) {
123             for (Entry<String, CharSequence> entry : regexpReplacements.entrySet()) {
124                 buf =
125                         new StringBuilder(Pattern.compile(entry.getKey()).matcher(buf)
126                                 .replaceAll(entry.getValue().toString()));
127             }
128         }
129 
130         // write accumulated data into parent
131         ctx.getCurrent().characters(buf, 0, buf.length());
132     }
133 
134     @Override
135     protected boolean parseTag(Tag tag, ParserContext ctx) {
136         buf = new StringBuilder(Parameters.DEFAULT_BUFFER_SIZE);
137         fragmentReplacements = new HashMap<>();
138         regexpReplacements = new HashMap<>();
139         includeTag = tag;
140         return true;
141     }
142 
143     private void processPage(String src, Tag tag, ParserContext ctx) throws IOException, HttpErrorPage {
144         String fragment = tag.getAttribute("fragment");
145         String xpath = tag.getAttribute("xpath");
146         String xslt = tag.getAttribute("stylesheet");
147 
148         DriverRequest httpRequest = ctx.getHttpRequest();
149         List<Renderer> rendererList = new ArrayList<>();
150         Driver driver;
151         String page;
152 
153         int idx = src.indexOf(PROVIDER_PATTERN);
154         int idxLegacyPattern = src.indexOf(LEGACY_PROVIDER_PATTERN);
155         if (idx < 0 && idxLegacyPattern < 0) {
156             page = src;
157             driver = httpRequest.getDriver();
158         } else if (idx >= 0) {
159 
160             int startIdx = idx + PROVIDER_PATTERN.length();
161             int endIndex = src.indexOf("})", startIdx);
162             String provider = src.substring(startIdx, endIndex);
163             page = src.substring(endIndex + "})".length());
164             driver = DriverFactory.getInstance(provider);
165             if (LOG.isWarnEnabled() && idx > 0) {
166                 LOG.warn("Invalid src attribute : [{}], src should start with [{}{}})]."
167                         + " First characters [{}] have been ignored", src, PROVIDER_PATTERN, provider,
168                         src.substring(0, idx));
169             }
170 
171         } else {
172             int startIdx = idxLegacyPattern + PROVIDER_PATTERN.length();
173             int endIndex = src.indexOf("})", startIdx);
174             String provider = src.substring(startIdx, endIndex);
175             page = src.substring(endIndex + "})".length());
176             driver = DriverFactory.getInstance(provider);
177             if (LOG.isWarnEnabled() && idxLegacyPattern > 0) {
178                 LOG.warn("Invalid src attribute : [{}], src should start with [{}{}})]."
179                         + " First characters [{}] have been ignored", src, PROVIDER_PATTERN, provider,
180                         src.substring(0, idxLegacyPattern));
181             }
182         }
183 
184         InlineCache ic = InlineCache.getFragment(src);
185         if (ic != null && !ic.isExpired()) {
186             String cache = ic.getFragment();
187             characters(cache, 0, cache.length());
188         } else {
189             EsiRenderer esiRenderer;
190             if (fragment != null) {
191                 esiRenderer = new EsiRenderer(page, fragment);
192             } else {
193                 esiRenderer = new EsiRenderer();
194             }
195             if (fragmentReplacements != null && !fragmentReplacements.isEmpty()) {
196                 esiRenderer.setFragmentsToReplace(fragmentReplacements);
197             }
198             rendererList.add(esiRenderer);
199             if (xpath != null) {
200                 rendererList.add(new XpathRenderer(xpath));
201             } else if (xslt != null) {
202                 rendererList.add(new XsltRenderer(xslt, driver, httpRequest));
203             }
204             CloseableHttpResponse response =
205                     driver.render(page, httpRequest.getOriginalRequest(),
206                             rendererList.toArray(new Renderer[rendererList.size()]));
207             outAdapter.append(HttpResponseUtils.toString(response));
208         }
209     }
210 
211     void addFragmentReplacement(String fragment, CharSequence replacement) {
212         fragmentReplacements.put(fragment, replacement);
213     }
214 
215     void addRegexpReplacement(String regexp, CharSequence replacement) {
216         regexpReplacements.put(regexp, replacement);
217     }
218 
219 }