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.util.Properties;
18  import java.util.concurrent.Executor;
19  import java.util.concurrent.LinkedBlockingQueue;
20  import java.util.concurrent.ThreadPoolExecutor;
21  import java.util.concurrent.TimeUnit;
22  
23  import org.apache.commons.lang3.StringUtils;
24  import org.esigate.Driver;
25  import org.esigate.events.Event;
26  import org.esigate.events.EventDefinition;
27  import org.esigate.events.EventManager;
28  import org.esigate.events.IEventListener;
29  import org.esigate.events.impl.RenderEvent;
30  import org.esigate.extension.Extension;
31  import org.esigate.extension.surrogate.CapabilitiesEvent;
32  import org.esigate.extension.surrogate.Surrogate;
33  import org.esigate.util.Parameter;
34  import org.esigate.util.ParameterInteger;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * This extension processes ESI directives, like :
40   * <p>
41   * &lt;esi:include src="$(PROVIDER{cms})/news" fragment="news_1"/&gt;
42   * <p>
43   * This extension implements multi-threaded processing, aka Parallel ESI.
44   * <p>
45   * It uses the following parameters :
46   * <ul>
47   * <li>esi_max_threads : Maximum number of thread allocated to run parallel esi requests. Default is 0 : parallel esi is
48   * disabled.</li>
49   * <li>esi_min_threads : Minimun number of allocated threads.</li>
50   * <li>esi_max_idle : Release threads after X seconds of idle.</li>
51   * <li>esi_max_queue : Maximum waiting esi requests (waiting for threads). When the limit is reached, new requests are
52   * refused.</li>
53   * </ul>
54   * 
55   * @author Nicolas Richeton
56   */
57  public class Esi implements Extension, IEventListener {
58      private static final Logger LOG = LoggerFactory.getLogger(Esi.class);
59      // esi_max_threads = 0 -> linear execution
60      public static final Parameter<Integer> MAX_THREADS = new ParameterInteger("esi_max_threads", 0);
61      public static final Parameter<Integer> MIN_THREADS = new ParameterInteger("esi_min_threads", 0);
62      public static final Parameter<Integer> IDLE = new ParameterInteger("esi_max_idle", 60);
63      public static final Parameter<Integer> MAX_QUEUE = new ParameterInteger("esi_max_queue", 10000);
64      private Executor executor;
65      public static final String[] CAPABILITIES = new String[] {"ESI/1.0", "ESI-Inline/1.0", "X-ESI-Fragment/1.0",
66              "X-ESI-Replace/1.0", "X-ESI-XSLT/1.0", "ESIGATE/4.0"};
67  
68      @Override
69      public boolean event(EventDefinition id, Event event) {
70          RenderEvent renderEvent = (RenderEvent) event;
71  
72          boolean doEsi = true;
73  
74          // ensure we should process esi
75          if (renderEvent.getHttpResponse() != null
76                  && renderEvent.getHttpResponse().containsHeader(Surrogate.H_X_ENABLED_CAPABILITIES)) {
77              String enabledCapabilities =
78                      renderEvent.getHttpResponse().getFirstHeader(Surrogate.H_X_ENABLED_CAPABILITIES).getValue();
79  
80              doEsi = false;
81              for (String capability : CAPABILITIES) {
82                  if (StringUtils.contains(enabledCapabilities, capability)) {
83                      doEsi = true;
84                      break;
85                  }
86              }
87  
88          }
89  
90          if (doEsi) {
91              renderEvent.getRenderers().add(new EsiRenderer(this.executor));
92          }
93  
94          // Continue processing
95          return true;
96      }
97  
98      @Override
99      public void init(Driver driver, Properties properties) {
100         driver.getEventManager().register(EventManager.EVENT_RENDER_PRE, this);
101 
102         driver.getEventManager().register(Surrogate.EVENT_SURROGATE_CAPABILITIES, new IEventListener() {
103             @Override
104             public boolean event(EventDefinition id, Event event) {
105                 CapabilitiesEvent capEvent = (CapabilitiesEvent) event;
106                 for (String capability : CAPABILITIES) {
107                     capEvent.getCapabilities().add(capability);
108                 }
109                 return true;
110             }
111         });
112 
113         // Load configuration
114         int maxThreads = MAX_THREADS.getValue(properties);
115         int minThreads = MIN_THREADS.getValue(properties);
116         int idle = IDLE.getValue(properties);
117         int maxQueue = MAX_QUEUE.getValue(properties);
118 
119         if (maxThreads == 0) {
120             this.executor = null;
121             LOG.info("Linear ESI processing enabled.");
122         } else {
123             this.executor =
124                     new ThreadPoolExecutor(minThreads, maxThreads, idle, TimeUnit.SECONDS,
125                             new LinkedBlockingQueue<Runnable>(maxQueue));
126 
127             LOG.info("Multi-threaded ESI processing enabled. Thread limit: {}, max idle {}.",
128                     String.valueOf(maxThreads), String.valueOf(idle));
129         }
130 
131     }
132 
133 }