package de.torisan.util.servletFilters;

import java.io.File;
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** A Filter implementation that can be used to set certain (HTTP 1.1 as well as HTTP 1.0) header of the response
 * which can improve cacheability of delivered content in the client's browser cache as well as in some proxy servers on the way.
 * Improving cacheability means decreasing load on the server an minimising bandwidth consumption. But it also means that an outdated 
 * version of a resource from a server might (intentfully in his case) be displayed to the client! 
 * 
 * @author Toralf Richter 
 */
public class SimpleBrowserCacheInfluencingFilter implements Filter {
 
 /** The filter configuration to read from */
 private FilterConfig fc;
 /** Flag indication to use / to not use ETags */
 private boolean useETag = false;
 /** Value holder needed in calculation of time to set on the Expires header */
 private int expiresCurrentPlusHours = 0;
 
 /** This method performs the actual filtering / modifications to the response headers. It will perfom the actions configured 
  * in the deployment descriptor of the context this filter implementation is used in on the resources indicated by the 
  * filter-mapping properties configured in the deployment descriptor.
  */
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest)req;
  HttpServletResponse response = (HttpServletResponse)res;
  
  String file = this.fc.getServletContext().getRealPath( 
   request.getRequestURI().substring( request.getContextPath().length() ) 
  );

  try {
   File resFile = new File(file);
   if (resFile.exists()) {
    response.setDateHeader("Last-Modified", resFile.getAbsoluteFile().lastModified());
   }
   if( this.expiresCurrentPlusHours>0 ) {
	response.setDateHeader("Expires", (System.currentTimeMillis()+((this.expiresCurrentPlusHours*3600)*1000L) ));
   }
   if( this.useETag ) {
   	if( !response.containsHeader("ETag") ) {
     response.setHeader("ETag", "W/\""+resFile.length()+"-"+resFile.getAbsoluteFile().lastModified()+"\"" );
   	}
   }
  } catch (Exception e) {
   // response.setHeader("X-Exception", e.getClass().getName() );

  }
  chain.doFilter(request, response);
 }

 /** The init method reads the FilterConfig, which is set up for the desired type of operation
  * using init params in the web.xml deployment descriptor of the servlet context. In the following doc section you will find 
  * some explanations on how to configure the filter properly via the web.xml deployment descriptor as well as some considerations 
  * on the headers that can be used / configured.
  * <br/>
  * Known init params for this sample implementation are "useETag" and "expires". "useETag" 
  * accepts pseudo-boolean values of textual "true" and "false". "useETag" can as well be omitted (which maps it's value to "false").
  * If "useETag" is set to true the filter will try so set an ETag header. If a previously set ETag header (generated by the container itself)
  * is already present, this will not be changed. 
  * If an *ETag* header is set / used it will be set as a composition of the resource's modification date (millis since epoch) 
  * and it's byte size on disk. 
  * <p/>
  * Considerations with ETag:<br/>
  * 1. Documentation says: Entity tags are normally "strong validators," but the protocol provides a mechanism to tag an entity tag 
  * as "weak." One can think of a strong validator as one that changes whenever the bits of an entity changes, while a weak value changes 
  * whenever the meaning of an entity changes. Alternatively, one can think of a strong validator as part of an identifier for a specific 
  * entity, while a weak validator is part of an identifier for a set of semantically equivalent entities.<br/>
  * 2. Tomcat itself when setting ETags set them as weak validator (prefixed with W/). This filter also sets ETags als weak validators because 
  * it seems more standard compliant. Consider the example: The bit order in an image file on disk changed, it's modifications time has been 
  * kept constant (e.g. by setting the mtime back to it's old value with a system tool). Since the the ETag here is composed of bit size 
  * (not a bit order hash) and mtime in millis since epoch, this would make the same ETag as before the change to the image file - although a 
  * change to the bit order in the file has occured. In most cases using a strong or weak validator ETag would not make much of a 
  * difference - yet in some it might.<br/>
  * 3. Some more issues: Even if the ETag is set to a certain value with this filter, they final response sent to the client might contain 
  * a different value. This is due to the container itself overwriting any previously set ETag at a later stage (after this filter has been 
  * applied). 
  * <p/>
  * "expires" takes a positive integer number as it's value. It can be omitted (which maps it's value to 
  * 0 and thereby instructs the filter to not set an *Expires* header. Any existing headers of the same name will 
  * remain in place though). The integer value of "expires" is used to set the Date value of the *Expires* header 
  * to the date value of the the current date plus x hours. <br/>
  * So setting expires=36 would set an *Expires* header on the response which equals the current date + 36 hours.
  * <p/>
  * Considerations with Expires header: Keep in mind that Expires is a HTTP 1.0 header. 
  * Sample filter configuration section for web.xml
  * <p/>
  * <pre>
  * &lt;filter&gt;
  * &lt;filter-name&gt;SimpleBrowserCacheInfluencingFilter&lt;/filter-name&gt;
  * &lt;filter-class&gt;SimpleBrowserCacheInfluencingFilter&lt;/filter-class&gt;
  * &lt;init-param&gt;
  *  &lt;param-name&gt;useETag&lt;/param-name&gt;
  *  &lt;param-value&gt;true&lt;/param-value&gt;
  * &lt;/init-param&gt;
  * &lt;init-param&gt;
  *  &lt;param-name&gt;expires&lt;/param-name&gt;
  *  &lt;param-value&gt;36&lt;/param-value&gt;
  * &lt;/init-param&gt; 
  * &lt;/filter&gt;
  * </pre>
  */
 public void init(FilterConfig filterConfig) {
  this.fc = filterConfig;
  this.useETag = new Boolean( this.fc.getInitParameter( "useETag" ) ).booleanValue(); 
   	
  if ( this.fc.getInitParameter( "expires" )!=null && this.fc.getInitParameter( "expires" ).length()>0 )  {
   try {	
   this.expiresCurrentPlusHours = Integer.parseInt( this.fc.getInitParameter( "expires" ), 10 );
   } catch( Exception e ) {	
   }
  }  
 }

 /** Simple implementation of the destroy method prescribed by the 
  * Filter interface.
  */
 public void destroy() {
  this.fc = null;
 }
 
}