KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > go > teaservlet > RegionCachingApplication


1 /* ====================================================================
2  * TeaServlet - Copyright (c) 1999-2000 Walt Disney Internet Group
3  * ====================================================================
4  * The Tea Software License, Version 1.1
5  *
6  * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Walt Disney Internet Group (http://opensource.go.com/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact opensource@dig.com.
31  *
32  * 5. Products derived from this software may not be called "Tea",
33  * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34  * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35  * written permission of the Walt Disney Internet Group.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
41  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  * ====================================================================
49  *
50  * For more information about Tea, please see http://opensource.go.com/.
51  */

52
53 package com.go.teaservlet;
54
55 import java.io.Serializable JavaDoc;
56 import java.util.*;
57 import java.net.*;
58 import java.rmi.Remote JavaDoc;
59 import java.rmi.RemoteException JavaDoc;
60 import java.lang.ref.*;
61 import com.go.trove.io.*;
62 import com.go.trove.log.Log;
63 import com.go.trove.util.*;
64 import com.go.trove.util.tq.*;
65 import com.go.tea.runtime.TemplateLoader;
66 import com.go.tea.runtime.Substitution;
67 import com.go.teaservlet.util.cluster.*;
68 import javax.servlet.ServletException JavaDoc;
69
70
71 /******************************************************************************
72  * Application that defines a cache function for templates. The cache is
73  * applied over a region within a template and is called like:
74  *
75  * <pre>
76  * cache() {
77  * // Cached template code and text goes here
78  * }
79  * </pre>
80  *
81  * The contents within the cache are used up to a configurable time-to-live
82  * value, specified in milliseconds. A specific TTL value can be passed as an
83  * argument to the cache function, in order to override the configured default.
84  * <p>
85  * Regions are keyed on enclosing template, region number, and HTTP query
86  * parameters. An optional secondary key may be passed in which helps to
87  * further identify the region. To pass in multiple values for the secondary
88  * key, pass in an array.
89  * <p>
90  * The cache function can be invoked multiple times within a template and the
91  * cache calls can be nested within each other. When templates are reloaded,
92  * the cached regions are dropped.
93  * <p>
94  * The RegionCachingApplication can also compress cached regions and send the
95  * GZIP compressed response to the client. The intention is not to reduce
96  * memory usage on the server, but to conserve bandwidth. If the client
97  * supports GZIP encoding, then it is eligible to receive a fully or partially
98  * GZIP compressed response.
99  * <p>
100  * If provided with cluster configuration information to pass along to the
101  * {@link com.go.teaservlet.util.cluster.ClusterManager}, status information
102  * may be shared between machines to compare cache sizes.
103  *
104  * @author Brian S O'Neill
105  * @version
106  * <!--$$Revision:--> 36 <!-- $-->, <!--$$JustDate:--> 01/07/05 <!-- $-->
107  */

108 public class RegionCachingApplication implements AdminApp {
109     private Log mLog;
110     private int mCacheSize;
111     private long mDefaultTTL;
112     private long mTimeout;
113     private String JavaDoc[] mHeaders;
114     private int mCompressLevel;
115
116     private ClusterManager mClusterManager;
117     private ClusterCacheInfo mInfo;
118
119     // Maps Templates to TemplateKeys
120
private Map mTemplateKeys = new IdentityMap();
121
122     // List of DepotLinks for linking TemplateLoaders and Depots. A Map isn't
123
// used here because the list will be small, usually just one element.
124
// After a template reload, the old Depots should be discarded as soon as
125
// possible in order to free up memory. Iterating over the list can pick
126
// up cleared references.
127
private List mDepots = new ArrayList(10);
128
129     // Shared by all Depots.
130
private TransactionQueue mTQ;
131
132     /**
133      * Accepts the following optional parameters:
134      *
135      * <pre>
136      * cache.size Amount of most recently used cache items to keep, which is
137      * 500 by default.
138      * default.ttl Default time-to-live of cached regions, in milliseconds.
139      * If left unspecified, default.ttl is 5000 milliseconds.
140      * timeout Maximum milliseconds to wait on cache before serving an
141      * expired region. Default value is 500 milliseconds.
142      * headers List of headers to use for all keys. i.e. User-Agent or
143      * Host.
144      * gzip Accepts a value from 0 to 9 to set compression level. When
145      * non-zero, GZIP compression is enabled for cached regions.
146      * A value of 1 offers fast compression, and a value of 9
147      * offers best compression. A value of 6 is the typical
148      * default used by GZIP.
149      *
150      * transactionQueue Properties for TransactionQueue that executes regions.
151      * max.threads Maximum thread count. Default is 100.
152      * max.size Maximum size of TransactionQueue. Default is 100.
153      * </pre>
154      */

155     public void init(ApplicationConfig config) throws ServletException JavaDoc {
156         mLog = config.getLog();
157
158         PropertyMap props = config.getProperties();
159         mCacheSize = props.getInt("cache.size", 500);
160         mDefaultTTL = props.getNumber
161             ("default.ttl", new Long JavaDoc(5000)).longValue();
162         mTimeout = props.getNumber("timeout", new Long JavaDoc(500)).longValue();
163
164         PropertyMap tqProps = props.subMap("transactionQueue");
165
166         int maxPool = tqProps.getInt("max.threads", 100);
167         ThreadPool tp = new ThreadPool(config.getName(), maxPool);
168         tp.setTimeout(5000);
169         tp.setIdleTimeout(60000);
170
171         mTQ = new TransactionQueue(tp, config.getName() + " TQ", 100, 100);
172         mTQ.applyProperties(tqProps);
173
174         String JavaDoc headers = props.getString("headers");
175
176         if (headers == null) {
177             mHeaders = null;
178         }
179         else {
180             StringTokenizer st = new StringTokenizer(headers, " ;,");
181             int count = st.countTokens();
182             if (count == 0) {
183                 mHeaders = null;
184             }
185             else {
186                 mHeaders = new String JavaDoc[count];
187                 for (int i=0; i<count; i++) {
188                     mHeaders[i] = st.nextToken();
189                 }
190             }
191         }
192
193         mCompressLevel = props.getInt("gzip", 0);
194
195         if (mCompressLevel < 0) {
196             mLog.warn("GZIP compression lowest level is 0. Level " +
197                       mCompressLevel + " interpretted as 0");
198             mCompressLevel = 0;
199         }
200         else if (mCompressLevel > 9) {
201             mLog.warn("GZIP compression highest level is 9. Level " +
202                       mCompressLevel + " interpretted as 9");
203             mCompressLevel = 9;
204         }
205
206         initCluster(config);
207     }
208     
209     public void destroy() {
210         if (mClusterManager != null) {
211             mClusterManager.killAuto();
212         }
213     }
214
215     /**
216      * Returns an instance of {@link RegionCachingContext}.
217      */

218     public Object JavaDoc createContext(ApplicationRequest request,
219                                 ApplicationResponse response) {
220         return new RegionCachingContextImpl(this, request, response);
221     }
222
223     /**
224      * Returns {@link RegionCachingContext}.class.
225      */

226     public Class JavaDoc getContextType() {
227         return RegionCachingContext.class;
228     }
229
230     public AppAdminLinks getAdminLinks() {
231
232         AppAdminLinks links = new AppAdminLinks(mLog.getName());
233         links.addAdminLink("Depot","system.teaservlet.Depot");
234         return links;
235     }
236
237
238     void cache(ApplicationRequest request,
239                ApplicationResponse response,
240                Substitution s)
241         throws Exception JavaDoc
242     {
243         cache(request, response, mDefaultTTL, null, s);
244     }
245
246     void cache(ApplicationRequest request,
247                ApplicationResponse response,
248                long ttlMillis,
249                Substitution s)
250         throws Exception JavaDoc
251     {
252         cache(request, response, ttlMillis, null, s);
253     }
254
255     void cache(ApplicationRequest request,
256                ApplicationResponse response,
257                long ttlMillis,
258                Object JavaDoc key,
259                Substitution s)
260         throws Exception JavaDoc
261     {
262         TemplateKey templateKey = getTemplateKey(request.getTemplate());
263
264         Object JavaDoc[] keyElements = {
265             templateKey,
266             s.getIdentifier(),
267             getHeaderValues(request),
268             request.getQueryString(),
269             key,
270         };
271         
272         key = new MultiKey(keyElements);
273             
274         ApplicationResponse.Command c =
275             new CacheCommand(s, templateKey, ttlMillis, key);
276         if (!response.insertCommand(c)) {
277             c.execute(request, response);
278         }
279     }
280
281     void nocache(ApplicationRequest request,
282                  ApplicationResponse response,
283                  Substitution s)
284         throws Exception JavaDoc
285     {
286         ApplicationResponse.Command c = new NoCacheCommand(s);
287         if (!response.insertCommand(c)) {
288             c.execute(request, response);
289         }
290     }
291
292     private TemplateKey getTemplateKey(TemplateLoader.Template template) {
293         synchronized (mTemplateKeys) {
294             TemplateKey key = (TemplateKey)mTemplateKeys.get(template);
295             if (key == null) {
296                 key = new TemplateKey(template);
297                 mTemplateKeys.put(template, key);
298             }
299             return key;
300         }
301     }
302
303     private String JavaDoc[] getHeaderValues(ApplicationRequest request) {
304         if (mHeaders == null) {
305             return null;
306         }
307
308         String JavaDoc[] headers = (String JavaDoc[])mHeaders.clone();
309         for (int i = headers.length; --i >= 0; ) {
310             headers[i] = request.getHeader(headers[i]);
311         }
312
313         return headers;
314     }
315
316     Depot getDepot(TemplateLoader.Template template) {
317         return getDepot(getTemplateKey(template));
318     }
319
320     Depot getDepot(TemplateKey key) {
321         TemplateLoader depotKey = key.getTemplateLoader();
322         if (depotKey == null) {
323             return null;
324         }
325
326         DepotLink link;
327         Object JavaDoc linkKey;
328         Depot depot;
329
330         synchronized (mDepots) {
331             int size = mDepots.size();
332             if (size == 1) {
333                 link = (DepotLink)mDepots.get(0);
334                 linkKey = link.get();
335                 if (linkKey == null) {
336                     // Remove cleared reference.
337
mDepots.clear();
338                 }
339                 else if (linkKey == depotKey) {
340                     return link.mDepot;
341                 }
342             }
343             else if (size > 1) {
344                 depot = null;
345                 Iterator it = mDepots.iterator();
346                 while (it.hasNext()) {
347                     link = (DepotLink)it.next();
348                     linkKey = link.get();
349                     if (linkKey == null) {
350                         // Remove cleared reference.
351
it.remove();
352                     }
353                     else if (linkKey == depotKey) {
354                         depot = link.mDepot;
355                         // Don't break loop: keep searching for cleared refs.
356
}
357                 }
358                 if (depot != null) {
359                     return depot;
360                 }
361             }
362
363             // If here, no Depot found. Make one.
364
depot = new Depot(null, mCacheSize, mTQ, mTimeout);
365             mDepots.add(new DepotLink(depotKey, depot));
366         }
367
368         return depot;
369     }
370
371
372     private void initCluster(ApplicationConfig config) {
373         
374         try {
375             String JavaDoc clusterName = config.getInitParameter("cluster.name");
376             if (clusterName != null) {
377                 mInfo = new ClusterCacheInfoImpl(clusterName, null);
378                 mClusterManager = ClusterManager
379                     .createClusterManager(config.getProperties(), mInfo);
380             }
381         }
382         catch(Exception JavaDoc e) {
383             mLog.warn("Failed to create ClusterManager.");
384             mLog.warn(e);
385         }
386     }
387
388     // Allows old templates to be garbage collected when reloaded.
389
private static class TemplateKey extends WeakReference {
390         TemplateKey(TemplateLoader.Template template) {
391             super(template);
392         }
393
394         public boolean equals(Object JavaDoc obj) {
395             if (this == obj) {
396                 return true;
397             }
398             if (obj instanceof TemplateKey) {
399                 TemplateKey other = (TemplateKey)obj;
400                 Object JavaDoc t = this.get();
401                 if (t != null) {
402                     return t == other.get();
403                 }
404             }
405             return false;
406         }
407
408         public int hashCode() {
409             Object JavaDoc t = get();
410             return (t == null) ? 0 : t.hashCode();
411         }
412
413         public String JavaDoc toString() {
414             TemplateLoader.Template t = (TemplateLoader.Template)get();
415             if (t != null) {
416                 return t.getName();
417             }
418             else {
419                 return super.toString();
420             }
421         }
422
423         TemplateLoader getTemplateLoader() {
424             TemplateLoader.Template t = (TemplateLoader.Template)get();
425             return (t != null) ? t.getTemplateLoader() : null;
426         }
427     }
428
429     private static class DepotLink extends WeakReference {
430         final Depot mDepot;
431
432         DepotLink(TemplateLoader loader, Depot depot) {
433             super(loader);
434             mDepot = depot;
435         }
436     }
437
438     private class CacheCommand implements ApplicationResponse.Command {
439         final Substitution mSub;
440         final TemplateKey mTemplateKey;
441         final long mTTLMillis;
442         final Object JavaDoc mKey;
443
444         CacheCommand(Substitution s, TemplateKey templateKey,
445                      long ttlMillis, Object JavaDoc key) {
446             mSub = s.detach();
447             mTemplateKey = templateKey;
448             mTTLMillis = ttlMillis;
449             mKey = key;
450         }
451
452         public void execute(ApplicationRequest request,
453                             ApplicationResponse response)
454             throws Exception JavaDoc
455         {
456             DetachedDataFactory factory =
457                 new DetachedDataFactory(this, response);
458
459             ApplicationResponse.DetachedData data;
460             Depot depot = getDepot(mTemplateKey);
461
462             if (depot != null) {
463                 data = (ApplicationResponse.DetachedData)
464                     depot.get(factory, mKey);
465                 if (mCompressLevel > 0 && !factory.mCalled) {
466                     // If the factory wasn't called, then this was a cache hit.
467
// Its likely it will be seen again, so take the time to
468
// compress.
469
if (data != null && request.isCompressionAccepted()) {
470                         try {
471                             data.compress(mCompressLevel);
472                         }
473                         catch (UnsatisfiedLinkError JavaDoc e) {
474                             // Native library to support compression not found.
475
mCompressLevel = 0;
476                             mLog.error(e);
477                         }
478                     }
479                 }
480             }
481             else {
482                 // This should never happen, but just in case, generate data
483
// without caching.
484
data = (ApplicationResponse.DetachedData)factory.create(mKey);
485             }
486             
487             if (data != null) {
488                 data.playback(request, response);
489             }
490         }
491
492         Log getLog() {
493             return mLog;
494         }
495     }
496
497     private static class DetachedDataFactory
498         implements Depot.PerishablesFactory
499     {
500         boolean mCalled;
501
502         private CacheCommand mCommand;
503         private ApplicationResponse mResponse;
504
505         DetachedDataFactory(CacheCommand c, ApplicationResponse response) {
506             mCommand = c;
507             mResponse = response;
508         }
509
510         public Object JavaDoc create(Object JavaDoc xxx) throws InterruptedException JavaDoc {
511             mCalled = true;
512             try {
513                 return mResponse.execDetached(mCommand.mSub);
514             }
515             catch (InterruptedException JavaDoc e) {
516                 mCommand.getLog().error(e);
517                 throw e;
518             }
519             catch (Exception JavaDoc e) {
520                 mCommand.getLog().error(e);
521                 throw new InterruptedException JavaDoc(e.getMessage());
522             }
523         }
524         
525         public long getValidDuration() {
526             return mCommand.mTTLMillis;
527         }
528     };
529
530     private class NoCacheCommand implements ApplicationResponse.Command {
531         private Substitution mSub;
532
533         NoCacheCommand(Substitution s) {
534             mSub = s.detach();
535         }
536
537         public void execute(ApplicationRequest request,
538                             ApplicationResponse response) {
539             try {
540                 mSub.detach().substitute(response.getHttpContext());
541             }
542             catch (Exception JavaDoc e) {
543                 mLog.error(e);
544             }
545         }
546     }
547
548     private class RegionCachingContextImpl
549         implements RegionCachingContext {
550
551         private final RegionCachingApplication mApp;
552         private final ApplicationRequest mRequest;
553         private final ApplicationResponse mResponse;
554
555         public RegionCachingContextImpl(RegionCachingApplication app,
556                                         ApplicationRequest request,
557                                         ApplicationResponse response) {
558             mApp = app;
559             mRequest = request;
560             mResponse = response;
561         }
562
563         /**
564          * Caches and reuses a region of a page. The cached region expies after
565          * a default time-to-live period has elapsed.
566          *
567          * @param s substitution block whose contents will be cached
568          */

569         public void cache(Substitution s) throws Exception JavaDoc {
570             mApp.cache(mRequest, mResponse, s);
571         }
572
573         /**
574          * Caches and reuses a region of a page. The cached region expies after
575          * a the specified time-to-live period has elapsed.
576          *
577          * @param ttlMillis maximum time to live of cached region, in milliseconds
578          * @param s substitution block whose contents will be cached
579          */

580         public void cache(long ttlMillis, Substitution s) throws Exception JavaDoc {
581             mApp.cache(mRequest, mResponse, ttlMillis, s);
582         }
583
584         /**
585          * Caches and reuses a region of a page. The cached region expies after
586          * a the specified time-to-live period has elapsed. An additional parameter
587          * is specified which helps to identify the uniqueness of the region.
588          *
589          * @param ttlMillis maximum time to live of cached region, in milliseconds
590          * @param key key to further identify cache region uniqueness
591          * @param s substitution block whose contents will be cached
592          */

593         public void cache(long ttlMillis, Object JavaDoc key, Substitution s)
594             throws Exception JavaDoc
595         {
596             mApp.cache(mRequest, mResponse, ttlMillis, key, s);
597         }
598
599         public void nocache(Substitution s) throws Exception JavaDoc {
600             mApp.nocache(mRequest, mResponse, s);
601         }
602
603         public RegionCachingApplication.RegionCacheInfo getRegionCacheInfo() {
604             return new RegionCacheInfo(mApp.getDepot(mRequest.getTemplate()));
605         }
606
607         public RegionCachingApplication.ClusterCacheInfo getClusterCacheInfo() {
608             mClusterManager.resolveServerNames();
609             return mInfo;
610         }
611     }
612
613     public interface ClusterCacheInfo extends Clustered {
614
615         public RegionCachingApplication.RegionCacheInfo getRegionCacheInfo()
616             throws RemoteException JavaDoc;
617     }
618
619     public class ClusterCacheInfoImpl extends ClusterHook
620         implements RegionCachingApplication.ClusterCacheInfo {
621         
622         ClusterCacheInfoImpl(String JavaDoc cluster, String JavaDoc server)
623             throws RemoteException JavaDoc {
624             super(cluster, server);
625         }
626
627         public RegionCachingApplication.RegionCacheInfo getRegionCacheInfo()
628             throws RemoteException JavaDoc {
629             int size = -1;
630             List depotList = RegionCachingApplication.this.mDepots;
631             if (depotList != null && (size = depotList.size()) > 0) {
632                 DepotLink link = (DepotLink)depotList.get(0);
633                 if (link != null) {
634                     return new RegionCacheInfo(link.mDepot);
635                 }
636             }
637             return null;
638         }
639     }
640
641     /**
642      * At the moment this class just contains a single int, but this may
643      * change as the application evolves.
644      */

645     public class RegionCacheInfo implements Serializable JavaDoc {
646         
647         private int size;
648
649         RegionCacheInfo(Depot depot) {
650             //mDepot = depot;
651
size = depot.size();
652         }
653
654         public int getSize() {
655             return size;
656         }
657     }
658 }
659
Popular Tags