dijous, 6 de febrer del 2014

Crear un filtre en una WebApp en JSF per a canviar el tipus de lletra

Recentment un client m'ha demanat de canviar el tipus de lletra d'una aplicació. Es tracta d'un portal fet amb JSF en el que els usuaris poden crear els seus continguts. En la majoria d'aquests, han usat el tipus de lletra "Helvetica", sense tenir en compte que es tracta d'una font sotmesa a llicència i no disponible a tots els sistemes.

Per a corregir-ho, enlloc d'entrar a tots els articles i modificar-ne el tipus de lletra (amb el risc d'oblidar-ne algun), el que hem fet ha estat crear un filtre que modifiqui la sortida cap al navegador substituïnt el tipus de lletra per "Arial".

S'ha pogut fer mitjançant tres classes:
  • FiltreCanviarFontDeHelveticaAArial, el filtre encarregat del processament
  • HttpServletResponseAmbBuffer, un wrapper de la resposta que permet actuar sobre un buffer enlloc de l'stream de sortida.
  • ServletOutputStreamCopier, l'stream usat com a buffer
A continuació hi ha el codi de les tres classes:


FiltreCanviarFontDeHelveticaAArial.java

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
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.HttpServletResponse;
import org.apache.commons.lang.StringUtils;

/**
 *
 * @author Oscar Pérez del Campo 
 */
public class FiltreCanviarFontDeHelveticaAArial implements Filter
{

  private static final boolean debug = true;
  // The filter configuration object we are associated with.  If
  // this value is null, this filter instance is not currently
  // configured. 
  private FilterConfig filterConfig = null;

  public FiltreCanviarFontDeHelveticaAArial()
  {
  }

  private void doBeforeProcessing(ServletRequest request, ServletResponse response)
          throws IOException, ServletException
  {
    if (debug)
    {
      log("FiltreCanviarFontDeHelveticaAArial:DoBeforeProcessing");
    }

  }

  private void doAfterProcessing(ServletRequest request, HttpServletResponseAmbBuffer response)
          throws IOException, ServletException
  {
    if (debug)
    {
      log("FiltreCanviarFontDeHelveticaAArial:DoAfterProcessing");
    }

    byte[] dades = response.getCopy();
    String strResposta = new String(dades);
    if (!StringUtils.isEmpty(strResposta))
    {
      strResposta = strResposta.replaceAll("font-family:[ ]*helvetica", "font-family: arial");
      response.substitueixResposta(strResposta);
    }
    
  }

  /**
   *
   * @param request The servlet request we are processing
   * @param response The servlet response we are creating
   * @param chain The filter chain we are processing
   *
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a servlet error occurs
   */
  public void doFilter(ServletRequest request, ServletResponse response,
          FilterChain chain)
          throws IOException, ServletException
  {

    if (debug)
    {
      log("FiltreCanviarFontDeHelveticaAArial:doFilter()");
    }

    OutputStream osOriginal = response.getOutputStream();
    
    HttpServletResponseAmbBuffer resp = new HttpServletResponseAmbBuffer((HttpServletResponse)response);
    doBeforeProcessing(request, resp);

    Throwable problem = null;
    try
    {
      chain.doFilter(request, resp);
    } catch (Throwable t)
    {
      // If an exception is thrown somewhere down the filter chain,
      // we still want to execute our after processing, and then
      // rethrow the problem after that.
      problem = t;
      t.printStackTrace();
    }

    doAfterProcessing(request, resp);
    
    osOriginal.write(resp.getCopy());

    // If there was a problem, we want to rethrow it if it is
    // a known type, otherwise log it.
    if (problem != null)
    {
      if (problem instanceof ServletException)
      {
        throw (ServletException) problem;
      }
      if (problem instanceof IOException)
      {
        throw (IOException) problem;
      }
      sendProcessingError(problem, resp);
    }
  }

  /**
   * Return the filter configuration object for this filter.
   */
  public FilterConfig getFilterConfig()
  {
    return (this.filterConfig);
  }

  /**
   * Set the filter configuration object for this filter.
   *
   * @param filterConfig The filter configuration object
   */
  public void setFilterConfig(FilterConfig filterConfig)
  {
    this.filterConfig = filterConfig;
  }

  /**
   * Destroy method for this filter
   */
  public void destroy()
  {
  }

  /**
   * Init method for this filter
   */
  public void init(FilterConfig filterConfig)
  {
    this.filterConfig = filterConfig;
    if (filterConfig != null)
    {
      if (debug)
      {
        log("FiltreCanviarFontDeHelveticaAArial:Initializing filter");
      }
    }
  }

  /**
   * Return a String representation of this object.
   */
  @Override
  public String toString()
  {
    if (filterConfig == null)
    {
      return ("FiltreCanviarFontDeHelveticaAArial()");
    }
    StringBuffer sb = new StringBuffer("FiltreCanviarFontDeHelveticaAArial(");
    sb.append(filterConfig);
    sb.append(")");
    return (sb.toString());
  }

  private void sendProcessingError(Throwable t, ServletResponse response)
  {
    String stackTrace = getStackTrace(t);

    if (stackTrace != null && !stackTrace.equals(""))
    {
      try
      {
        response.setContentType("text/html");
        PrintStream ps = new PrintStream(response.getOutputStream());
        PrintWriter pw = new PrintWriter(ps);
        pw.print("\n\nError\n\n\n"); //NOI18N

        // PENDING! Localize this for next official release
        pw.print("

The resource did not process correctly

\n
\n");
        pw.print(stackTrace);
        pw.print("
\n"); //NOI18N pw.close(); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } else { try { PrintStream ps = new PrintStream(response.getOutputStream()); t.printStackTrace(ps); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } } public static String getStackTrace(Throwable t) { String stackTrace = null; try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.close(); sw.close(); stackTrace = sw.getBuffer().toString(); } catch (Exception ex) { } return stackTrace; } public void log(String msg) { filterConfig.getServletContext().log(msg); } }


HttpServletResponseAmbBuffer.java

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 *
 * @author Oscar Pérez del Campo
 */
public class HttpServletResponseAmbBuffer extends HttpServletResponseWrapper
{

  private ServletOutputStream outputStream;
  private PrintWriter writer;
  private ServletOutputStreamCopier copier;

  public HttpServletResponseAmbBuffer(HttpServletResponse response) throws IOException
  {
    super(response);
  }

  @Override
  public ServletOutputStream getOutputStream() throws IOException
  {
    if (writer != null)
    {
      throw new IllegalStateException("getWriter() has already been called on this response.");
    }

    if (outputStream == null)
    {
      outputStream = getResponse().getOutputStream();
      copier = new ServletOutputStreamCopier(outputStream);
    }

    return copier;
  }

  @Override
  public PrintWriter getWriter() throws IOException
  {
    if (writer == null)
    {
      writer = new PrintWriter(new OutputStreamWriter(this.getOutputStream(), getResponse().getCharacterEncoding()), true);
    }

    return writer;
  }

  @Override
  public void flushBuffer() throws IOException
  {
    if (writer != null)
    {
      writer.flush();
    } else if (outputStream != null)
    {
      copier.flush();
    }
  }

  public byte[] getCopy()
  {
    if (copier != null)
    {
      return copier.getCopy();
    } else
    {
      return new byte[0];
    }
  }

  public void substitueixResposta(String str) throws IOException
  {
    copier.substitueixContinguts(str);
  }
  
}


ServletOutputStreamCopier.java

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;

/**
 *
 * @author Oscar Pérez del Campo 
 */
public class ServletOutputStreamCopier extends ServletOutputStream
{

  private OutputStream outputStream;

  public ServletOutputStreamCopier(OutputStream outputStream)
  {
    this.outputStream = new ByteArrayOutputStream(2048);
  }

  @Override
  public void write(int b) throws IOException
  {
    outputStream.write(b);
  }

  public byte[] getCopy()
  {
    return ((ByteArrayOutputStream) outputStream).toByteArray();
  }

  void substitueixContinguts(String str) throws IOException
  {
    if (outputStream != null)
    {
      outputStream.close();
    }
    outputStream = new ByteArrayOutputStream();
    outputStream.write(str.getBytes());
  }
}