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.esi;
16  
17  import java.io.IOException;
18  import java.io.Writer;
19  import java.util.Map;
20  import java.util.regex.Pattern;
21  
22  import org.apache.http.HttpStatus;
23  import org.esigate.HttpErrorPage;
24  import org.esigate.Parameters;
25  import org.esigate.Renderer;
26  import org.esigate.impl.DriverRequest;
27  import org.esigate.parser.Parser;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * Retrieves a resource from the provider application and parses it to find ESI tags to be replaced by contents from
33   * other applications.
34   * 
35   * For more information about ESI language specification, see <a href="http://www.w3.org/TR/esi-lang">Edge Side
36   * Include</a>
37   * 
38   * @author Francois-Xavier Bonnet
39   */
40  public class EsiRenderer implements Renderer, Appendable {
41  
42      private static final Logger LOG = LoggerFactory.getLogger(EsiRenderer.class);
43  
44      private static final Pattern PATTERN = Pattern
45              .compile("(<esi:\\w+((\\s+\\w+(\\s*=\\s*(?:\".*?\"|'.*?'|[^'\">\\s]+))?)+\\s*|\\s*)/?>)|(</esi:[^>]*>)");
46      private static final Pattern PATTERN_COMMENTS = Pattern.compile("(<!--esi)|(-->)");
47  
48      private final Parser parser = new Parser(PATTERN, IncludeElement.TYPE, CommentElement.TYPE, RemoveElement.TYPE,
49              VarsElement.TYPE, ChooseElement.TYPE, WhenElement.TYPE, OtherwiseElement.TYPE, TryElement.TYPE,
50              AttemptElement.TYPE, ExceptElement.TYPE, InlineElement.TYPE, ReplaceElement.TYPE, FragmentElement.TYPE);
51  
52      private final Parser parserComments = new Parser(PATTERN_COMMENTS, Comment.TYPE);
53  
54      private Writer out;
55  
56      private Map<String, CharSequence> fragmentsToReplace;
57  
58      private final String page;
59  
60      private final String name;
61  
62      private boolean write = true;
63  
64      private boolean found = false;
65  
66      public String getName() {
67          return name;
68      }
69  
70      public void setWrite(boolean write) {
71          this.write = write;
72      }
73  
74      /**
75       * Constructor used to render a complete page.
76       */
77      public EsiRenderer() {
78          page = null;
79          name = null;
80      }
81  
82      /**
83       * Constructor used to render a fragment Retrieves a fragment inside a page.<br>
84       * 
85       * Extracts html between <code>&lt;esi:fragment name="myFragment"&gt;</code> and <code>&lt;/esi:fragment&gt;</code>
86       * 
87       * @param page
88       * @param name
89       */
90      public EsiRenderer(String page, String name) {
91          this.page = page;
92          this.name = name;
93          write = false;
94      }
95  
96      public Map<String, CharSequence> getFragmentsToReplace() {
97          return fragmentsToReplace;
98      }
99  
100     public void setFragmentsToReplace(Map<String, CharSequence> fragmentsToReplace) {
101         this.fragmentsToReplace = fragmentsToReplace;
102     }
103 
104     @Override
105     public void render(DriverRequest originalRequest, String content, Writer outWriter) throws IOException,
106             HttpErrorPage {
107         if (name != null) {
108             LOG.debug("Rendering fragment {} in page {}", name, page);
109         }
110         this.out = outWriter;
111         if (content == null) {
112             return;
113         }
114 
115         // Pass 1. Remove esi comments
116         StringBuilder contentWithoutComments = new StringBuilder(Parameters.DEFAULT_BUFFER_SIZE);
117         parserComments.setHttpRequest(originalRequest);
118         parserComments.parse(content, contentWithoutComments);
119 
120         // Pass 2. Process ESI
121         parser.setHttpRequest(originalRequest);
122         parser.parse(contentWithoutComments, this);
123 
124         if (name != null && !this.found) {
125             throw new HttpErrorPage(HttpStatus.SC_BAD_GATEWAY, "Fragment " + name + " not found", "Fragment " + name
126                     + " not found");
127         }
128     }
129 
130     @Override
131     public Appendable append(CharSequence csq) throws IOException {
132         if (write) {
133             out.append(csq);
134         }
135         return this;
136     }
137 
138     @Override
139     public Appendable append(char c) throws IOException {
140         if (write) {
141             out.append(c);
142         }
143         return this;
144     }
145 
146     @Override
147     public Appendable append(CharSequence csq, int start, int end) throws IOException {
148         if (write) {
149             out.append(csq, start, end);
150         }
151         return this;
152     }
153 
154     public boolean isWrite() {
155         return this.write;
156     }
157 
158     public void setFound(boolean found) {
159         this.found = found;
160 
161     }
162 
163 }