001    package railo.runtime.tag;
002    
003    
004    import java.io.IOException;
005    import java.io.OutputStream;
006    
007    import javax.servlet.http.HttpServletResponse;
008    
009    import org.apache.oro.text.regex.MalformedPatternException;
010    
011    import railo.commons.io.IOUtil;
012    import railo.commons.io.cache.CacheEntry;
013    import railo.commons.io.res.Resource;
014    import railo.commons.io.res.util.ResourceUtil;
015    import railo.commons.lang.StringUtil;
016    import railo.runtime.PageContextImpl;
017    import railo.runtime.cache.legacy.CacheItem;
018    import railo.runtime.config.ConfigImpl;
019    import railo.runtime.exp.Abort;
020    import railo.runtime.exp.ApplicationException;
021    import railo.runtime.exp.DeprecatedException;
022    import railo.runtime.exp.ExpressionException;
023    import railo.runtime.exp.PageException;
024    import railo.runtime.exp.TemplateException;
025    import railo.runtime.ext.tag.BodyTagImpl;
026    import railo.runtime.functions.cache.CacheGet;
027    import railo.runtime.functions.cache.CachePut;
028    import railo.runtime.functions.cache.CacheRemove;
029    import railo.runtime.functions.cache.Util;
030    import railo.runtime.functions.dateTime.GetHttpTimeString;
031    import railo.runtime.op.Caster;
032    import railo.runtime.tag.util.DeprecatedUtil;
033    import railo.runtime.type.StructImpl;
034    import railo.runtime.type.dt.DateTime;
035    import railo.runtime.type.dt.DateTimeImpl;
036    import railo.runtime.type.dt.TimeSpan;
037    import railo.runtime.type.dt.TimeSpanImpl;
038    import railo.runtime.writer.CFMLWriterImpl;
039    
040    /**
041    * Speeds up page rendering when dynamic content does not have to be retrieved each time a user accesses
042    *   the page. To accomplish this, cfcache creates temporary files that contain the static HTML returned from
043    *   a CFML page. You can use cfcache for simple URLs and URLs that contain URL parameters.
044    *
045    *
046    *
047    **/
048    public final class Cache extends BodyTagImpl {
049    
050    
051    
052    
053            private static final TimeSpan TIMESPAN_FAR_AWAY = new TimeSpanImpl(1000000000,1000000000,1000000000,1000000000); 
054            private static final TimeSpan TIMESPAN_0 = new TimeSpanImpl(0,0,0,0); 
055    
056    
057            /**  */
058            private Resource directory;
059    
060    
061            /** Specifies the protocol used to create pages from cache. Either http:// or https://. The default
062            **              is http://. */
063            private String protocol;
064    
065            /**  */
066            private String expireurl;
067    
068            /**  */
069            private int action=CACHE;
070    
071            /** When required for basic authentication, a valid username. */
072            private String username;
073    
074            /** When required for basic authentication, a valid password. */
075            private String password;
076        
077            private TimeSpan timespan=TIMESPAN_FAR_AWAY;
078            private TimeSpan idletime=TIMESPAN_0;
079        
080    
081            /**  */
082            private int port=-1;
083    
084            private DateTimeImpl now;
085    
086    
087            private String body;
088            private String _id;
089            private Object id;
090            private String name;
091            private String key;
092    
093    
094            private boolean hasBody;
095    
096    
097            private boolean doCaching;
098    
099    
100            private CacheItem cacheItem;
101    
102    
103            private String cachename;
104            private Object value;
105            private boolean throwOnError;
106            private String metadata;
107        
108        private static final int CACHE=0;
109        private static final int CACHE_SERVER=1;
110        private static final int CACHE_CLIENT=2;
111        private static final int FLUSH=3;
112        private static final int CONTENT=4;
113    
114        private static final int GET=5;
115        private static final int PUT=6;
116        
117        /**
118        * @see javax.servlet.jsp.tagext.Tag#release()
119        */
120        public void release()   {
121            super.release();
122            directory=null;
123            username=null;
124            password=null;
125            protocol=null;
126            expireurl=null;
127            action=CACHE;
128            port=-1;
129            timespan=TIMESPAN_FAR_AWAY;
130            idletime=TIMESPAN_0;
131            body=null;
132            hasBody=false;
133            id=null;
134            key=null;
135            body=null;
136            doCaching=false;
137            cacheItem=null;
138            name=null;
139            cachename=null;
140            throwOnError=false;
141            value=null;
142            metadata=null;
143        }
144            
145            /**
146             * @deprecated this attribute is deprecated and will ignored in this tag
147             * @param obj
148             * @throws DeprecatedException
149             */
150            public void setTimeout(Object obj) throws DeprecatedException {
151                    DeprecatedUtil.tagAttribute(pageContext,"Cache","timeout");
152            }
153    
154            /** set the value directory
155            *  
156            * @param directory value to set
157            **/
158            public void setDirectory(String directory) throws ExpressionException   {
159                    this.directory=ResourceUtil.toResourceExistingParent(pageContext, directory);
160            }
161            public void setCachedirectory(String directory) throws ExpressionException      {
162                    setDirectory(directory);
163            }
164            
165    
166            /** set the value protocol
167            *  Specifies the protocol used to create pages from cache. Either http:// or https://. The default
168            *               is http://.
169            * @param protocol value to set
170            **/
171            public void setProtocol(String protocol)        {
172                    if(protocol.endsWith("://"))protocol=protocol.substring(0,protocol.indexOf("://"));
173                    this.protocol=protocol.toLowerCase();
174            }
175            
176            private String getProtocol()    {
177                    if(StringUtil.isEmpty(protocol)) {
178                            return pageContext. getHttpServletRequest().getScheme();
179                    }
180                    return protocol;
181            }
182    
183            /** set the value expireurl
184            *  
185            * @param expireurl value to set
186            **/
187            public void setExpireurl(String expireurl)      {
188                    this.expireurl=expireurl;
189            }
190    
191            /** set the value action
192            *  
193            * @param action value to set
194             * @throws ApplicationException 
195            **/
196            public void setAction(String action) throws ApplicationException        {
197            action=action.toLowerCase().trim();
198            if(action.equals("get"))              this.action=GET; 
199            else if(action.equals("put"))              this.action=PUT; 
200            
201            
202            else if(action.equals("cache"))         this.action=CACHE; 
203            else if(action.equals("clientcache"))   this.action=CACHE_CLIENT;
204            else if(action.equals("servercache"))   this.action=CACHE_SERVER;
205            else if(action.equals("flush"))         this.action=FLUSH;
206            else if(action.equals("optimal"))       this.action=CACHE;
207            
208            else if(action.equals("client-cache"))   this.action=CACHE_CLIENT;
209            else if(action.equals("client_cache"))   this.action=CACHE_CLIENT;
210            
211            else if(action.equals("server-cache"))   this.action=CACHE_SERVER;
212            else if(action.equals("server_cache"))   this.action=CACHE_SERVER;
213            
214            else if(action.equals("content"))       this.action=CONTENT;
215            else if(action.equals("content_cache"))  this.action=CONTENT;
216            else if(action.equals("contentcache"))  this.action=CONTENT;
217            else if(action.equals("content-cache"))  this.action=CONTENT;
218            else throw new ApplicationException("invalid value for attribute action for tag cache ["+action+"], " +
219                            "valid actions are [get,put,cache, clientcache, servercache, flush, optimal, contentcache]");
220            
221            //get: get an object from the cache.
222            //put: Add an object to the cache.
223            
224    
225            
226            
227            }
228    
229            /** set the value username
230            *  When required for basic authentication, a valid username.
231            * @param username value to set
232            **/
233            public void setUsername(String username)        {
234                    this.username=username;
235            }
236            
237            /** set the value password
238            *  When required for basic authentication, a valid password.
239            * @param password value to set
240            **/
241            public void setPassword(String password)        {
242                    this.password=password;
243            }
244            
245            public void setKey(String key)  {
246                    this.key=key;
247            }
248    
249            /** set the value port
250            *  
251            * @param port value to set
252            **/
253            public void setPort(double port)        {
254                    this.port=(int)port;
255            }
256            
257            public int getPort() {
258                    if(port<=0) return pageContext. getHttpServletRequest().getServerPort();
259                    return port;
260            }
261    
262        /**
263         * @param timespan The timespan to set.
264         * @throws PageException 
265         */
266        public void setTimespan(TimeSpan timespan) {
267            this.timespan = timespan;
268        }
269        
270        
271        /**
272            * @throws PageException 
273         * @see javax.servlet.jsp.tagext.Tag#doStartTag()
274            */
275            public int doStartTag() throws PageException    {
276                    now = new DateTimeImpl(pageContext.getConfig());
277                    try {
278                    if(action==CACHE) {
279                        doClientCache();
280                        doServerCache();
281                    }
282                    else if(action==CACHE_CLIENT)   doClientCache();
283                    else if(action==CACHE_SERVER)   doServerCache();
284                    else if(action==FLUSH)                  doFlush();
285                    else if(action==CONTENT) return doContentCache();
286                    else if(action==GET) doGet();
287                    else if(action==PUT) doPut();
288                    
289                            return EVAL_PAGE;
290                    }
291                    catch(Exception e) {
292                            throw Caster.toPageException(e);
293                    }
294            }
295    
296            /**
297            * @see javax.servlet.jsp.tagext.BodyTag#doInitBody()
298            */
299            public void doInitBody()        {
300                    
301            }
302    
303            /**
304            * @see javax.servlet.jsp.tagext.BodyTag#doAfterBody()
305            */
306            public int doAfterBody()        {
307                    //print.out("doAfterBody");
308                    if(bodyContent!=null)body=bodyContent.getString();
309                    return SKIP_BODY;
310            }
311        
312            /**
313            * @throws PageException 
314             * @see javax.servlet.jsp.tagext.Tag#doStartTag()
315            */
316            
317    
318        /**
319         * @throws PageException 
320         * @see javax.servlet.jsp.tagext.Tag#doEndTag()
321            */
322            public int doEndTag() throws PageException      {//print.out("doEndTag"+doCaching+"-"+body);
323                    if(doCaching && body!=null) {
324                    try {
325                                    writeCacheResource(cacheItem, body);
326                                    pageContext.write(body);
327                            }
328                    catch (IOException e) {
329                                    throw Caster.toPageException(e);
330                            }
331                    }
332                    return EVAL_PAGE;
333            }
334    
335            private void doClientCache() {
336            pageContext.setHeader("Last-Modified",GetHttpTimeString.call(pageContext,now));
337            
338            if(timespan!=null) {
339                DateTime expires = getExpiresDate();
340                pageContext.setHeader("Expires",GetHttpTimeString.call(pageContext,expires));
341            }
342        }
343    
344            private void doServerCache() throws IOException, PageException {
345                    if(hasBody)hasBody=!StringUtil.isEmpty(body);
346                    
347            // call via cfcache disable debugger output
348                pageContext.getDebugger().setOutput(false);
349                    
350            HttpServletResponse rsp = pageContext.getHttpServletResponse();
351            
352            // generate cache resource matching request object
353            CacheItem ci = generateCacheResource(null,false);
354            
355            // use cached resource
356            if(ci.isValid(timespan)){ //if(isOK(cacheResource)){
357                    if(pageContext. getHttpServletResponse().isCommitted()) return;
358                    
359                    OutputStream os=null;
360                    try {
361                    ci.writeTo(os=getOutputStream(),rsp.getCharacterEncoding());
362                            //IOUtil.copy(is=cacheResource.getInputStream(),os=getOutputStream(),false,false);
363                } 
364                finally {
365                    IOUtil.flushEL(os);
366                    IOUtil.closeEL(os);
367                    ((PageContextImpl)pageContext).getRootOut().setClosed(true);
368                }
369                    throw new Abort(Abort.SCOPE_REQUEST);
370            }
371            
372            // call page again and
373            //MetaData.getInstance(getDirectory()).add(ci.getName(), ci.getRaw());
374            
375            PageContextImpl pci = (PageContextImpl)pageContext;
376            ((CFMLWriterImpl)pci.getRootOut()).doCache(ci);
377                    
378        }
379    
380            /*private boolean isOK(Resource cacheResource) {
381                    return cacheResource.exists() && (cacheResource.lastModified()+timespan.getMillis()>=System.currentTimeMillis());
382            }*/
383    
384            private int doContentCache() throws IOException, PageException {
385    
386            // file
387            cacheItem = generateCacheResource(key,true);
388            // use cache
389            if(cacheItem.isValid(timespan)){
390                    pageContext.write(cacheItem.getValue());
391                    doCaching=false;
392                return SKIP_BODY;
393            }
394            doCaching=true;
395            return EVAL_BODY_BUFFERED;
396            }
397    
398            private void doGet() throws PageException, IOException {
399                    required("cache", "id", id);
400                    required("cache", "name", name);
401                    String id=Caster.toString(this.id);
402                    if(metadata==null){
403                            pageContext.setVariable(name,CacheGet.call(pageContext, id,throwOnError,cachename));    
404                    }
405                    else {
406                            railo.commons.io.cache.Cache cache = 
407                                    Util.getCache(pageContext.getConfig(),cachename,ConfigImpl.CACHE_DEFAULT_OBJECT);
408                            CacheEntry entry = throwOnError?cache.getCacheEntry(Util.key(id)):cache.getCacheEntry(Util.key(id),null);
409                            if(entry!=null){
410                                    pageContext.setVariable(name,entry.getValue());
411                                    pageContext.setVariable(metadata,entry.getCustomInfo());
412                            }
413                            else {
414                                    pageContext.setVariable(metadata,new StructImpl());
415                            }
416                    }
417                    
418                    
419                    
420                    
421                    
422                    
423                    
424                    
425            }
426            private void doPut() throws PageException {
427                    required("cache", "id", id);
428                    required("cache", "value", value);
429                    TimeSpan ts = timespan;
430                    TimeSpan it = idletime;
431                    if(ts==TIMESPAN_FAR_AWAY)ts=TIMESPAN_0;
432                    if(it==TIMESPAN_FAR_AWAY)it=TIMESPAN_0; 
433                    CachePut.call(pageContext, Caster.toString(id),value,ts,it,cachename);
434            }
435            
436    
437            
438    
439            private void doFlush() throws IOException, MalformedPatternException, PageException {
440                    if(id!=null){
441                            required("cache", "id", id);
442                            CacheRemove.call(pageContext, id,throwOnError,cachename);
443                    }
444                    else if(StringUtil.isEmpty(expireurl)) {
445                            CacheItem.flushAll(pageContext,directory,cachename);
446                    }
447                    else {
448                            CacheItem.flush(pageContext,directory,cachename,expireurl);
449                            
450                            //ResourceUtil.removeChildrenEL(getDirectory(),(ResourceNameFilter)new ExpireURLFilter(expireurl));
451                    }
452        }
453    
454    
455        private CacheItem generateCacheResource(String key, boolean useId) throws IOException {
456            return CacheItem.getInstance(pageContext,_id,key,useId,directory,cachename,timespan);
457            }
458    
459            
460    
461            private void writeCacheResource(CacheItem cacheItem, String result) throws IOException {
462                    cacheItem.store(result);
463                    //IOUtil.write(cacheItem.getResource(), result,"UTF-8", false); 
464                    //MetaData.getInstance(cacheItem.getDirectory()).add(cacheItem.getName(), cacheItem.getRaw());
465            }
466            
467    
468        private DateTime getExpiresDate() {
469                    return new DateTimeImpl(pageContext,getExpiresTime(),false);
470            }
471    
472            private long getExpiresTime() {
473                    return now.getTime()+(timespan.getMillis());
474            }
475            
476            private OutputStream getOutputStream() throws PageException, IOException {
477            try {
478                    return ((PageContextImpl)pageContext).getResponseStream();
479            } 
480            catch(IllegalStateException ise) {
481                throw new TemplateException("content is already send to user, flush");
482            }
483        }
484            
485            
486            /**
487         * sets if tag has a body or not
488         * @param hasBody
489         */
490        public void hasBody(boolean hasBody) {
491           this.hasBody=hasBody;
492        }
493    
494            /**
495             * @param id the id to set
496             */
497            public void set_id(String _id) {
498                    this._id = _id;
499            }
500            
501            public void setId(Object id) {
502                    this.id = id;
503            }
504            
505            public void setName(String name) {
506                    this.name = name;
507            }
508            public void setCachename(String cachename) {
509                    this.cachename = cachename;
510            }
511            
512            /**
513             * @param throwOnError the throwOnError to set
514             */
515            public void setThrowonerror(boolean throwOnError) {
516                    this.throwOnError = throwOnError;
517            }
518            
519            public void setValue(Object value) {
520                    this.value = value;
521            }
522            
523            /**
524             * @param idletime the idletime to set
525             */
526            public void setIdletime(TimeSpan idletime) {
527                    this.idletime = idletime;
528            }
529    
530            /**
531             * @param metadata the metadata to set
532             */
533            public void setMetadata(String metadata) {
534                    this.metadata = metadata;
535            }
536    }
537