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