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  package org.esigate.extension.parallelesi;
16  
17  import java.io.IOException;
18  import java.io.Writer;
19  import java.util.Map;
20  import java.util.concurrent.ExecutionException;
21  import java.util.concurrent.Executor;
22  import java.util.concurrent.Future;
23  import java.util.concurrent.TimeUnit;
24  import java.util.concurrent.TimeoutException;
25  import java.util.regex.Pattern;
26  
27  import org.apache.http.HttpStatus;
28  import org.esigate.HttpErrorPage;
29  import org.esigate.Renderer;
30  import org.esigate.impl.DriverRequest;
31  import org.esigate.parser.future.FutureAppendable;
32  import org.esigate.parser.future.FutureAppendableAdapter;
33  import org.esigate.parser.future.FutureParser;
34  import org.esigate.parser.future.StringBuilderFutureAppendable;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * Retrieves a resource from the provider application and parses it to find ESI tags to be replaced by contents from
40   * other applications.
41   * 
42   * For more information about ESI language specification, see <a href="http://www.w3.org/TR/esi-lang">Edge Side
43   * Include</a>
44   * 
45   * <p>
46   * This class is based on EsiRenderer
47   * 
48   * @see org.esigate.esi.EsiRenderer
49   * 
50   * @author Nicolas Richeton
51   */
52  public class EsiRenderer implements Renderer, FutureAppendable {
53  
54      private static final Logger LOG = LoggerFactory.getLogger(EsiRenderer.class);
55      /**
56       * Key for the executor for future tasks. This is used with parser#setData().
57       */
58      public static final String DATA_EXECUTOR = "executor";
59  
60      private static final Pattern PATTERN = Pattern
61              .compile("(<esi:\\w+((\\s+\\w+(\\s*=\\s*(?:\".*?\"|'.*?'|[^'\">\\s]+))?)+\\s*|\\s*)/?>)|(</esi:[^>]*>)");
62      private static final Pattern PATTERN_COMMENTS = Pattern.compile("(<!--esi)|(-->)");
63  
64      private final FutureParser parser = new FutureParser(PATTERN, IncludeElement.TYPE, CommentElement.TYPE,
65              RemoveElement.TYPE, VarsElement.TYPE, ChooseElement.TYPE, WhenElement.TYPE, OtherwiseElement.TYPE,
66              TryElement.TYPE, AttemptElement.TYPE, ExceptElement.TYPE, InlineElement.TYPE, ReplaceElement.TYPE,
67              FragmentElement.TYPE);
68  
69      private final FutureParser parserComments = new FutureParser(PATTERN_COMMENTS, Comment.TYPE);
70  
71      private Map<String, CharSequence> fragmentsToReplace;
72  
73      private final String page;
74  
75      private final String name;
76  
77      private boolean write = true;
78  
79      private boolean found = false;
80  
81      private FutureAppendableAdapter futureOut;
82  
83      private Executor executor;
84  
85      public String getName() {
86          return name;
87      }
88  
89      public void setWrite(boolean write) {
90          this.write = write;
91      }
92  
93      /**
94       * Constructor used to render a complete page.
95       * 
96       * @param executor
97       *            Executor to use for background operations or null if single-thread operations.
98       */
99      public EsiRenderer(Executor executor) {
100         this.page = null;
101         this.name = null;
102         this.executor = executor;
103     }
104 
105     /**
106      * Constructor used to render a fragment Retrieves a fragment inside a page.<br>
107      * 
108      * Extracts html between <code>&lt;esi:fragment name="myFragment"&gt;</code> and <code>&lt;/esi:fragment&gt;</code>
109      * 
110      * @param page
111      * @param name
112      * @param executor
113      *            Executor to use for background operations or null if single-thread operations.
114      */
115     public EsiRenderer(String page, String name, Executor executor) {
116         this.page = page;
117         this.name = name;
118         this.write = false;
119         this.executor = executor;
120     }
121 
122     public Map<String, CharSequence> getFragmentsToReplace() {
123         return fragmentsToReplace;
124     }
125 
126     public void setFragmentsToReplace(Map<String, CharSequence> fragmentsToReplace) {
127         this.fragmentsToReplace = fragmentsToReplace;
128     }
129 
130     @Override
131     public void render(DriverRequest originalRequest, String content, Writer out) throws IOException, HttpErrorPage {
132         if (name != null) {
133             LOG.debug("Rendering fragment {} in page {}", name, page);
134         }
135         this.futureOut = new FutureAppendableAdapter(out);
136         if (content == null) {
137             return;
138         }
139 
140         try {
141             // Pass 1. Remove esi comments
142             StringBuilderFutureAppendable contentWithoutComments = new StringBuilderFutureAppendable();
143             parserComments.setHttpRequest(originalRequest);
144             parserComments.setData(DATA_EXECUTOR, this.executor);
145             parserComments.parse(content, contentWithoutComments);
146             CharSequence contentWithoutCommentsResult;
147 
148             contentWithoutCommentsResult = contentWithoutComments.get();
149 
150             // Pass 2. Process ESI
151             parser.setHttpRequest(originalRequest);
152             parser.setData(DATA_EXECUTOR, this.executor);
153             parser.parse(contentWithoutCommentsResult, this);
154 
155             if (name != null && !this.found) {
156                 throw new HttpErrorPage(HttpStatus.SC_BAD_GATEWAY, "Fragment " + name + " not found", "Fragment "
157                         + name + " not found");
158             }
159 
160             this.futureOut.performAppends();
161 
162         } catch (ExecutionException e) {
163             throw new IOException(e);
164         }
165     }
166 
167     @Override
168     public FutureAppendable enqueueAppend(Future<CharSequence> csq) {
169         if (this.write) {
170             this.futureOut.enqueueAppend(csq);
171         }
172         return this;
173     }
174 
175     public boolean isWrite() {
176         return this.write;
177     }
178 
179     public void setFound(boolean found) {
180         this.found = found;
181 
182     }
183 
184     @Override
185     public FutureAppendable performAppends() throws IOException, HttpErrorPage {
186         return this.futureOut.performAppends();
187     }
188 
189     @Override
190     public boolean hasPending() {
191         return this.futureOut.hasPending();
192     }
193 
194     @Override
195     public FutureAppendable performAppends(int timeout, TimeUnit unit) throws IOException, HttpErrorPage,
196             TimeoutException {
197         return this.futureOut.performAppends(timeout, unit);
198     }
199 
200 }