Monday, January 19, 2015

Prestruts filter


import java.io.IOException;

import net.mycom.myapp.misc.myappProperties;

import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import org.apache.struts2.dispatcher.ng.filter.*;

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

public class PreStrutsFilter  implememycom Filter {
 private static Logger logger = Logger.getLogger(PreStrutsFilter.class);
 
 Filter strutsFilter = new StrutsPrepareAndExecuteFilter();
 private String bypassStrutsExp;
 private String bypassStrutsExpMobile;
 private String bypassVaadin;
 private ServletContext ctx;

 @Override
    public void destroy() {
  strutsFilter.destroy();    
    }

 @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  if(req instanceof HttpServletRequest){
   HttpServletRequest httpReq = (HttpServletRequest)req;
   String uri = httpReq.getRequestURI();
   
   String ctxRoot = this.ctx.getContextPath();
   uri = uri.replace(ctxRoot, "");
   
   if(!uri.matches(bypassStrutsExp) &&
      !uri.matches(bypassStrutsExpMobile) &&
      !uri.matches(bypassVaadin)
      ){
    
    // Browser cache images
    int dot = uri.lastIndexOf(".");
    if ((dot > 0) &&
        (uri.substring(dot).equalsIgnoreCase(".jpg") ||
         uri.substring(dot).equalsIgnoreCase(".jpeg") ||
         uri.substring(dot).equalsIgnoreCase(".gif") ||
         uri.substring(dot).equalsIgnoreCase(".png"))) {
     try {
      ((HttpServletResponse)res).setHeader("Cache-Control", "max-age=" + myappProperties.getCacheControlMaxAge());
     }
     catch (Exception e) {
      ((HttpServletResponse)res).setHeader("Cache-Control", "max-age=120");
      logger.error("Error retrieving cacheControlMaxAge", e);
     }
    }
    
    putMDC(httpReq); // Log user
    strutsFilter.doFilter(req, res, chain);
    MDC.clear(); // Remove from Mapped Diagnostic Context
   }
   else{
    chain.doFilter(req, res);
   }
  }
  else{
   chain.doFilter(req, res);
  }
    }

 @Override
    public void init(FilterConfig cfg) throws ServletException {
  strutsFilter.init(cfg);
  this.bypassStrutsExp = cfg.getInitParameter("struts_bypass_expression");
  this.bypassStrutsExpMobile = cfg.getInitParameter("struts_bypass_expression_mobile");
  this.bypassVaadin = cfg.getInitParameter("struts_bypass_vaadin");
  this.ctx = cfg.getServletContext();
    }
 
 private void putMDC(HttpServletRequest request) {
  Long accountId = (Long)request.getSession().getAttribute("accountId");
  String sessionId = request.getSession().getId();
  
  // If parameter exist put into Mapped Diagnostic Context
  if (accountId != null) { MDC.put("accountId", accountId); }
  if (sessionId != null) { MDC.put("sessionId", sessionId); }
 }
}

Vaadin app


package net.mycom.vaadin;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.mycom.myapp.domain.Account;
import net.mycom.myapp.misc.myappProperties;
import net.mycom.myapp.util.HibernateUtil;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.vaadin.Application;
import com.vaadin.terminal.gwt.server.HttpServletRequestListener;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.RichTextArea;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.CloseEvent;

public class mycomVaadinApplication extends Application implememycom HttpServletRequestListener {
 private static final long serialVersionUID = 1L;
 private static final String ERR_NITEFLIRT = "err_niteflirt";
 private static final String ERR_SCRIPT = "err_script";
 private static final String ERR_NONE = "err_none";
 private static final String ERR_NOACCOUNTID = "err_noAccountId";
 private static final String ERR_MAXSIZE = "err_maxsize";
 private static final String ERR_UNKNOWN = "err_unknown";
 private static ThreadLocal threadLocal = new ThreadLocal();
 public static Logger logger = Logger.getRootLogger();
 private static Integer dbProfilePageHtmlSize;
 Long accountId=0L;
 private transient HttpServletResponse response;
 private transient HttpServletRequest request;
 @Override
 public void init() {
  if (accountId == null || accountId == 0L) {
   try {
    String req = request.getRequestURI();
    response.sendRedirect(req.substring(0, req.indexOf("/VAADIN")) + "/home");
   } catch (IOException io) {
    
   }
   return;
  }
  String req = request.getRequestURL().toString();
  req = req.substring(0, req.indexOf("/VAADIN"));
    
  Window mainWindow = new Window("My Profile " + accountId);
  final RichTextArea ta = new RichTextArea();
  ta.setHeight("500");
  ta.setWidth("800");
  ta.setValue(getTextAreaContent());
  VerticalLayout layout=new VerticalLayout();
  Button saveButton = new Button();
  saveButton.addListener(new ClickListener() { 
   @Override
   public void buttonClick(ClickEvent event) {
    switch(saveTextAreaContent((String)ta.getValue())) {
    case ERR_NONE:
     logger.info("saving rich text area content=" + ta.getValue());
     getMainWindow().showNotification("Content saved");
     break;
    case ERR_SCRIPT:
     getMainWindow().showNotification("Not Saved. Cannot use script or javascript tags");
     break;
    case ERR_NITEFLIRT:
     getMainWindow().showNotification("Not Saved. Cannot use NITEFLIRT");
     break;
    case ERR_MAXSIZE:
     getMainWindow().showNotification("Not Saved. Content size exceeded maximum size. Reduce and try again");
     break;
    case ERR_UNKNOWN:
     getMainWindow().showNotification("Not Saved. Unknown error; please contact system administrator");
     break;
    default:
     getMainWindow().showNotification("Not Saved. No account Id...could not save");
    }
   } 
          });
  saveButton.setStyleName("vaadinButton");
  layout.addComponent(ta);
  layout.addComponent(saveButton);
  mainWindow.addComponent(layout);
  // Close the application if the main window is closed.
  mainWindow.addListener(new Window.CloseListener(){
     @Override
      public void windowClose(CloseEvent e) {
         logger.info("Closing the application");
         close();
      } 
  });
  setMainWindow(mainWindow);
 }
  @Override     
   public void onRequestStart(HttpServletRequest request, HttpServletResponse response) {
   accountId = (Long) request.getSession().getAttribute("accountId");
   this.response = response;
   this.request = request;
      mycomVaadinApplication.setInstance(this);   
   }  
  @Override     
   public void onRequestEnd(HttpServletRequest request, HttpServletResponse response) {
     threadLocal.remove();     
   } 
  // Set the current application instance  
   public static void setInstance(mycomVaadinApplication application) {      
       threadLocal.set(application);   
   }
   // @return the current application instance    
   public static mycomVaadinApplication getInstance() {   
     return threadLocal.get();  
   } 
   
   private String getTextAreaContent() {
     if ((Long)request.getSession().getAttribute("accountId") == null) {
    logger.info("no account id");
    return null;
   }
   logger.info("Retrieving html for accountId=" + (Long)request.getSession().getAttribute("accountId"));
   Session session = HibernateUtil.getCurremycomession();
   Account myAcc = (Account)session.get(Account.class, (Long)request.getSession().getAttribute("accountId"));
   session.close();
   
   return myAcc.getProfilePageHtml() == null ? "" : myAcc.getProfilePageHtml();
   }
   private String saveTextAreaContent(String html) {
     if ((Long)request.getSession().getAttribute("accountId") == null) {
    logger.info("no account id");
    return ERR_NOACCOUNTID;
   }
     try {
      if (!StringUtils.isEmpty(html) && html.length() > getDbProfilePageHtmlSize()) {
       logger.info("profile page too large");
       return ERR_MAXSIZE;
      }
     } catch (Exception e) {
      logger.error("max size property", e);
      return ERR_UNKNOWN;
     }
     String htmlNoWhiteSpaces = StringUtils.deleteWhitespace(html);
     if (htmlNoWhiteSpaces.toLowerCase().indexOf("javascript") > -1 || 
       htmlNoWhiteSpaces.toLowerCase().indexOf(" -1 || 
       htmlNoWhiteSpaces.toLowerCase().indexOf("/script") > -1) {
      logger.info("validation failed; found script tag");
      return ERR_SCRIPT;
     }
     if (htmlNoWhiteSpaces.toLowerCase().indexOf("niteflirt") > -1) {
      logger.info("validation failed; found niteflirt");
      return ERR_NITEFLIRT;
     }
     logger.info("saving -->" + html);
   Session session = HibernateUtil.getCurremycomession();
   Account myAcc = (Account)session.get(Account.class, (Long)request.getSession().getAttribute("accountId"));
   myAcc.setProfilePageHtml(html);
   Transaction tran = session.beginTransaction();
   session.saveOrUpdate(myAcc);
   tran.commit();
   session.close();
   return ERR_NONE;
   }
   public Integer getDbProfilePageHtmlSize () throws Exception {
     if (dbProfilePageHtmlSize == null) {
      dbProfilePageHtmlSize = retrieveDbProfilePageHtmlSize();
     }
      return dbProfilePageHtmlSize;
     
  }
   private synchronized Integer retrieveDbProfilePageHtmlSize()  {
    return 10000;
    /*
     Connection connection = null;
         int size =0;
         ResultSet rs=null;
         Statement st=null;
      try {
          Class.forName( "com.mysql.jdbc.Driver");
          connection = DriverManager.getConnection("jdbc:mysql:" + myappProperties.myappConnectionUrl(), myappProperties.myappUsername(), myappProperties.myappPassword());
          st = connection.createStatement();
          String sql = "select * from ACCOUNT LIMIT 1";
          rs = st.executeQuery(sql);
          ResultSetMetaData metadata = rs.getMetaData();
          int colCount = metadata.getColumnCount();
          for(int i=0; i < colCount; i++) {
           String name = metadata.getColumnName(i + 1);
           size = metadata.getColumnDisplaySize(i+1);
           String type =metadata.getColumnTypeName(i+1); 
           logger.debug("Column name: [" + name + "]; type: [" + type
               + "]; size: [" + size + "]");
           if (!StringUtils.isEmpty(name) && name.trim().equalsIgnoreCase("profile_page_html"))
            return new Integer(size);
          }
      } catch (SQLException e) {
          logger.error("sqlexception ", e);
      } catch (ClassNotFoundException e) {
       logger.error("classnotfoundexception ", e);
      } catch (Exception e) {
       logger.error("exception", e);
      } finally {
       try{
        st.close();
        rs.close();
        connection.close();
       } catch (Exception e) {}
      }
      return new Integer(size);
      */
   }
}

Hibernateutil


import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;

public class HibernateUtil {
 private static Logger logger = Logger.getRootLogger();
 
    private static SessionFactory sessionFactory;
    private static ServiceRegistry serviceRegistry;
 public static final String serviceUpdateUserName = "MYDOMAIN-SRVC";
 public static final String webUpdateUserName = "MYDOMAIN-WEB";
 public static final String customerServiceUpdateUserName = "MYDOMAIN-CS";
 public static final String batchUpdateUserName = "MYDOMAIN-BATCH";
 public static final ThreadLocal threadBoundSession = new ThreadLocal();
 
    public static void setConnectionParameters(String connectionUrl, String username, String password, String showSql) {
        logger.info("Hibernate connection: url=" + connectionUrl + " username=" + username);
        
  Configuration config = new Configuration();
  config.configure("hibernate_MYDOMAIN.cfg.xml");
  config.setProperty("hibernate.connection.url","jdbc:mysql:"+connectionUrl);
  config.setProperty("hibernate.connection.username",username);
  config.setProperty("hibernate.connection.password",password);
  config.setProperty("hibernate.show_sql", showSql);
  
  serviceRegistry = new ServiceRegistryBuilder().applySettings(config.getProperties()).buildServiceRegistry();  
  sessionFactory = config.buildSessionFactory(serviceRegistry);
  
        logger.info("Hibernate connection completed");
 }

    public static SessionFactory getSessionFactory() {
     return sessionFactory;
    }
    
    public static Session getCurreMYCOMession(){
     Session session = threadBoundSession.get();
     if(session == null || session.isOpen() == false){
      session = sessionFactory.openSession();
      threadBoundSession.set(session);
     }
     return session;
    }

 public static Session getCurreMYCOMessionIfNotNull() {
  return threadBoundSession.get();
 }
 
 public static void removeCurreMYCOMession(){
  threadBoundSession.remove();
 }

 public static void closeCurreMYCOMession(){
     Session session = threadBoundSession.get();
  if ((session != null) && session.isOpen())
   session.close();
 }
}

hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
 "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
 "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
 <session-factory>
  <!-- Database connection settings -->
  <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
  <property name="connection.url">x</property>
  <property name="connection.username">x</property>
  <property name="connection.password">x</property>

  <!-- SQL dialect -->
  <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
  
  <!-- Enable Hibernate's automatic session context management -->
  <property name="current_session_context_class">thread</property>
  
  <!-- Second-level cache http://ehcache.org/documentation/integrations/hibernate -->
  <property name="hibernate.cache.use_second_level_cache">true</property>
  <property name="hibernate.cache.use_query_cache">true</property>
  <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
  <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
  
  
  <!-- Setup C3P0 pooling -->
  <property name="connection.provider_class">org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider</property>
  <property name="hibernate.c3p0.acquire_increment">1</property> <!-- Batched connection creates -->
  <property name="hibernate.c3p0.initialPoolSize">0</property>
  <property name="hibernate.c3p0.min_size">0</property>
  <property name="hibernate.c3p0.max_size">50</property>
  <property name="hibernate.c3p0.timeout">300</property> <!-- seconds a Connection can remain pooled but unused before being discarded -->

  <!-- Echo all executed SQL to stdout -->
  <property name="show_sql">true</property>

  <mapping resource="net/mycom/myapp/domain/SmsType.hbm.xml" />
  
 </session-factory>
</hibernate-configuration>

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="false" monitoring="autodetect"
         dynamicConfig="true">
 <!-- timeToIdleSeconds & timeToLiveSeconds are not used if eternal is true --> 
 <defaultCache
  maxElementsInMemory="10000"
        eternal="true"
        overflowToDisk="false"
        diskPersistent="false"
        memoryStoreEvictionPolicy="LRU"/>
 <cache name="net.mycom.myapp.domain.Parameter"
  maxElementsInMemory="200" 
  eternal="true"
  overflowToDisk="false" />    
 <cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
  maxElementsInMemory="3000000" eternal="true" timeToIdleSeconds="0"
  timeToLiveSeconds="0" overflowToDisk="false" />
 <cache name="org.hibernate.cache.internal.StandardQueryCache"
  maxElementsInMemory="3000000" eternal="true" timeToIdleSeconds="0"
  timeToLiveSeconds="0" overflowToDisk="false" />
</ehcache>

ehcache.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="1.7">

    <xs:element name="ehcache">
        <xs:complexType>
            <xs:sequence>
                <xs:element maxOccurs="1" minOccurs="0" ref="diskStore"/>
                <xs:element maxOccurs="1" minOccurs="0" ref="transactionManagerLookup"/>
                <xs:element maxOccurs="1" minOccurs="0" ref="cacheManagerEventListenerFactory"/>
                <xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerProviderFactory"/>
                <xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerListenerFactory"/>
                <xs:element maxOccurs="1" minOccurs="0" ref="terracottaConfig"/>
                <xs:element maxOccurs= "1" minOccurs="0" ref="defaultCache"/>
                <xs:element maxOccurs="unbounded" minOccurs="0" ref="cache"/>
            </xs:sequence>
            <xs:attribute name="name" use="optional"/>
            <xs:attribute default="true" name="updateCheck" type="xs:boolean" use="optional"/>
            <xs:attribute default="autodetect" name="monitoring" type="monitoringType" use="optional"/>
            <xs:attribute default="true" name="dynamicConfig" type="xs:boolean" use="optional"/>
            <xs:attribute default="15" name="defaultTransactionTimeoutInSeconds" type="xs:integer" use="optional"/>
            <xs:attribute default="0" name="maxBytesLocalHeap" type="memoryUnitOrPercentage" use="optional"/>
            <xs:attribute default="0" name="maxBytesLocalOffHeap" type="memoryUnit" use="optional"/>
            <xs:attribute default="0" name="maxBytesLocalDisk" type="memoryUnit" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="diskStore">
        <xs:complexType>
            <xs:attribute name="path" use="optional"/>
        </xs:complexType>
    </xs:element>
     <xs:element name="transactionManagerLookup">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheManagerEventListenerFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheManagerPeerProviderFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheManagerPeerListenerFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="terracottaConfig">
        <xs:complexType>
            <xs:sequence>
                <xs:element maxOccurs="1" minOccurs="0" name="tc-config">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:any maxOccurs="unbounded" minOccurs="0" processContents="skip"/>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
            <xs:attribute default="localhost:9510" name="url" use="optional"/>
            <xs:attribute name="rejoin" type="xs:boolean" use="optional" default="false"/>
        </xs:complexType>
    </xs:element>
    <!-- add clone support for addition of cacheExceptionHandler. Important! -->
    <xs:element name="defaultCache">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="pinning"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="elementValueComparator"/>
            </xs:sequence>
            <xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
            <xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
            <xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
            <xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
            <xs:attribute name="eternal" type="xs:boolean" use="required"/>
            <xs:attribute name="maxElementsInMemory" type="xs:integer" use="optional"/>
            <xs:attribute name="maxEntriesLocalHeap" type="xs:integer" use="optional"/>
            <xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
            <xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
            <xs:attribute name="overflowToDisk" type="xs:boolean" use="required"/>
            <xs:attribute name="timeToIdleSeconds" type="xs:integer" use="optional"/>
            <xs:attribute name="timeToLiveSeconds" type="xs:integer" use="optional"/>
            <xs:attribute name="maxElementsOnDisk" type="xs:integer" use="optional"/>
            <xs:attribute name="maxEntriesLocalDisk" type="xs:integer" use="optional"/>
            <xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off"/>
            <xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
            <xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cache">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="pinning"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="searchable"/>
                <xs:element minOccurs="0" maxOccurs="1" ref="elementValueComparator"/>
            </xs:sequence>
            <xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
            <xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
            <xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
            <xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
            <xs:attribute name="eternal" type="xs:boolean" use="required"/>
            <xs:attribute name="maxElementsInMemory" type="xs:integer" use="optional"/>
            <xs:attribute name="maxEntriesLocalHeap" type="xs:integer" use="optional"/>
            <xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
            <xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
            <xs:attribute name="name" type="xs:string" use="required"/>
            <xs:attribute name="overflowToDisk" type="xs:boolean" use="required"/>
            <xs:attribute name="timeToIdleSeconds" type="xs:integer" use="optional"/>
            <xs:attribute name="timeToLiveSeconds" type="xs:integer" use="optional"/>
            <xs:attribute name="maxElementsOnDisk" type="xs:integer" use="optional"/>
            <xs:attribute name="maxEntriesLocalDisk" type="xs:integer" use="optional"/>
            <xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off" />
            <xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="logging" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
            <xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
            <xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
            <xs:attribute default="0" name="maxBytesLocalHeap" type="memoryUnitOrPercentage" use="optional"/>
            <xs:attribute default="0" name="maxBytesLocalOffHeap" type="memoryUnitOrPercentage" use="optional"/>
            <xs:attribute default="0" name="maxBytesLocalDisk" type="memoryUnitOrPercentage" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheEventListenerFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
            <xs:attribute name="listenFor" use="optional" type="notificationScope" default="all"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="bootstrapCacheLoaderFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheExtensionFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheExceptionHandlerFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheLoaderFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="cacheDecoratorFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="searchAttribute">
        <xs:complexType>
            <xs:attribute name="name" use="required" type="xs:string"/>
            <xs:attribute name="expression" type="xs:string"/>
            <xs:attribute name="class" type="xs:string"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>

    <xs:element name="searchable">
      <xs:complexType>
        <xs:sequence>
          <xs:element minOccurs="0" maxOccurs="unbounded" ref="searchAttribute"/>
        </xs:sequence>
        <xs:attribute name="keys" use="optional" type="xs:boolean" default="true"/>
        <xs:attribute name="values" use="optional" type="xs:boolean" default="true"/>
      </xs:complexType>
    </xs:element>

    <xs:element name="pinning">
        <xs:complexType>
            <xs:attribute name="store" use="required" type="pinningStoreType"/>
        </xs:complexType>
    </xs:element>

    <xs:element name="terracotta">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" maxOccurs="1" ref="nonstop"/>
            </xs:sequence>
            <xs:attribute name="clustered" use="optional" type="xs:boolean" default="true"/>
            <xs:attribute name="valueMode" use="optional" type="terracottaCacheValueType" default="serialization"/>
            <xs:attribute name="coherentReads" use="optional" type="xs:boolean" default="true"/>
            <xs:attribute name="localKeyCache" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="localKeyCacheSize" use="optional" type="xs:positiveInteger" default="300000"/>
            <xs:attribute name="orphanEviction" use="optional" type="xs:boolean" default="true"/>
            <xs:attribute name="orphanEvictionPeriod" use="optional" type="xs:positiveInteger" default="4"/>
            <xs:attribute name="copyOnRead" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="coherent" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="consistency" use="optional" type="consistencyType" default="eventual"/>
            <xs:attribute name="synchronousWrites" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="storageStrategy" use="optional" type="storageStrategyType" default="DCV2"/>
            <xs:attribute name="concurrency" use="optional" type="xs:nonNegativeInteger" default="0"/>
            <xs:attribute name="localCacheEnabled" use="optional" type="xs:boolean" default="true"/>
        </xs:complexType>
    </xs:element>
    <xs:simpleType name="consistencyType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="strong" />
            <xs:enumeration value="eventual" />
        </xs:restriction>
    </xs:simpleType>
    <xs:element name="nonstop">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" maxOccurs="1" ref="timeoutBehavior"/>
            </xs:sequence>
            <xs:attribute name="enabled" use="optional" type="xs:boolean" default="true"/>
            <xs:attribute name="immediateTimeout" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="timeoutMillis" use="optional" type="xs:positiveInteger" default="30000"/>
        </xs:complexType>
    </xs:element>
    <xs:element name="timeoutBehavior">
        <xs:complexType>
            <xs:attribute name="type" use="optional" type="timeoutBehaviorType" default="exception"/>
            <xs:attribute name="properties" use="optional" default=""/>
            <xs:attribute name="propertySeparator" use="optional" default=","/>
        </xs:complexType>
    </xs:element>
    <xs:simpleType name="timeoutBehaviorType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="noop" />
            <xs:enumeration value="exception" />
            <xs:enumeration value="localReads" />
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="monitoringType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="autodetect"/>
            <xs:enumeration value="on"/>
            <xs:enumeration value="off"/>
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="pinningStoreType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="localHeap" />
            <xs:enumeration value="localMemory" />
            <xs:enumeration value="inCache" />
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="terracottaCacheValueType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="serialization" />
            <xs:enumeration value="identity" />
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="storageStrategyType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="classic" />
            <xs:enumeration value="DCV2" />
        </xs:restriction>
    </xs:simpleType>

    <xs:simpleType name="transactionalMode">
        <xs:restriction base="xs:string">
            <xs:enumeration value="off"/>
            <xs:enumeration value="xa_strict"/>
            <xs:enumeration value="xa"/>
            <xs:enumeration value="local"/>
        </xs:restriction>
    </xs:simpleType>

    <xs:element name="cacheWriter">
        <xs:complexType>
            <xs:sequence >
                <xs:element minOccurs="0" maxOccurs="1" ref="cacheWriterFactory"/>
            </xs:sequence>
            <xs:attribute name="writeMode" use="optional" type="writeModeType" default="write-through"/>
            <xs:attribute name="notifyListenersOnException" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="minWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
            <xs:attribute name="maxWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
            <xs:attribute name="rateLimitPerSecond" use="optional" type="xs:nonNegativeInteger" default="0"/>
            <xs:attribute name="writeCoalescing" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="writeBatching" use="optional" type="xs:boolean" default="false"/>
            <xs:attribute name="writeBatchSize" use="optional" type="xs:positiveInteger" default="1"/>
            <xs:attribute name="retryAttempts" use="optional" type="xs:nonNegativeInteger" default="0"/>
            <xs:attribute name="retryAttemptDelaySeconds" use="optional" type="xs:nonNegativeInteger" default="1"/>
            <xs:attribute name="writeBehindConcurrency" use="optional" type="xs:nonNegativeInteger" default="1"/>
            <xs:attribute name="writeBehindMaxQueueSize" use="optional" type="xs:nonNegativeInteger" default="0"/>
        </xs:complexType>
    </xs:element>
    <xs:simpleType name="writeModeType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="write-through" />
            <xs:enumeration value="write-behind" />
        </xs:restriction>
    </xs:simpleType>
    <xs:element name="cacheWriterFactory">
        <xs:complexType>
            <xs:attribute name="class" use="required"/>
            <xs:attribute name="properties" use="optional"/>
            <xs:attribute name="propertySeparator" use="optional"/>
        </xs:complexType>
    </xs:element>

    <xs:element name="copyStrategy">
        <xs:complexType>
            <xs:attribute name="class" use="required" type="xs:string" />
        </xs:complexType>
    </xs:element>

    <xs:element name="elementValueComparator">
        <xs:complexType>
            <xs:attribute name="class" use="required" type="xs:string" />
        </xs:complexType>
    </xs:element>

    <xs:simpleType name="notificationScope">
        <xs:restriction base="xs:string">
            <xs:enumeration value="local"/>
            <xs:enumeration value="remote"/>
            <xs:enumeration value="all"/>
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="memoryUnit">
        <xs:restriction base="xs:token">
            <xs:pattern value="[0-9]+[bBkKmMgG]?"/>
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="memoryUnitOrPercentage">
        <xs:restriction base="xs:token">
            <xs:pattern value="([0-9]+[bBkKmMgG]?|100%|[0-9]{1,2}%)"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

Application Listener
package net.mycom.myapp.application.listener;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import net.mycom.myapp.misc.MycomCsProperties;
import net.mycom.myapp.util.HibernateUtil;

@WebListener
public class MycomApplicationListener implements ServletContextListener {
 public static Logger logger;
 private ServletContext ctx;

 public void contextInitialized(ServletContextEvent ctxEv) {
  PropertyConfigurator.configure("/Mycom/customerservice/log4j.properties");
  logger = Logger.getRootLogger();
        this.ctx = ctxEv.getServletContext();
  
        // Setup Mycom database
        try {
         net.mycom.myapp.util.HibernateUtil.setConnectionParameters(MycomCsProperties.MycomConnectionUrl(),
           MycomCsProperties.MycomUsername(),
           MycomCsProperties.MycomPassword(),
           MycomCsProperties.MycomShowSql());
         net.nts.cos.util.HibernateUtil.setConnectionParameters(MycomCsProperties.cosConnectionUrl(),
           MycomCsProperties.cosUsername(),
           MycomCsProperties.cosPassword(), 
           MycomCsProperties.MycomShowSql());
        } catch (Exception e) { logger.error("Failed to setup parameters",e); }
    }

 public void contextDestroyed(ServletContextEvent ctxEv) {
     net.mycom.myapp.util.HibernateUtil.getSessionFactory().close();
     net.mycom.cos.util.HibernateUtil.getSessionFactory().close();
     deregisterJdbcConnections();        
    }
 
 private void deregisterJdbcConnections() {
  // This manually de-registers JDBC driver, which prevents Tomcat 7 from
  // complaining about memory leaks wrto this class
  logger.info("Shutting down JDBC drivers");
  Enumeration drivers = DriverManager.getDrivers();
  while (drivers.hasMoreElements()) {
   Driver driver = drivers.nextElement();
   try {
    DriverManager.deregisterDriver(driver);
    logger.info(String.format("deregistering jdbc driver: %s",
      driver));
   } catch (SQLException e) {
    logger.warn(
      String.format("Error deregistering driver %s", driver),
      e);
   }
  }
 }

}

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>MyWebsite</display-name>
  <listener>
   <listener-class>net.mycom.myapp.application.listener.MycomApplicationListener</listener-class>
  </listener>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  <filter>
    <filter-name>connectionFilter</filter-name>
    <filter-class>net.mycom.myapp.filter.HibernateSessionRequestFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>connectionFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>preStruts2</filter-name>
    <filter-class>net.mycom.myapp.filter.PreStrutsFilter</filter-class>
    <init-param>
     <param-name>struts_bypass_expression</param-name>
     <param-value>^(/img/.*)$</param-value>
    </init-param>
    <init-param>
     <param-name>struts_bypass_expression_mobile</param-name>
     <param-value>^(/css/mobile/images/.*)$</param-value>
    </init-param>
   <init-param>
     <param-name>struts_bypass_vaadin</param-name>
     <param-value>^(/VAADIN/.*)$</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>preStruts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <context-param>
   <description>Vaadin production mode</description>
   <param-name>productionMode</param-name>
   <param-value>true</param-value>
  </context-param>
   <servlet>
  <servlet-name>Vaadin Application</servlet-name>
   <servlet-class>
     com.vaadin.terminal.gwt.server.ApplicationServlet
   </servlet-class>
   <init-param>
      <description>Vaadin application class to start</description>
      <param-name>application</param-name>
      <param-value>
        net.nts.vaadin.NtsVaadinApplication
      </param-value>
  </init-param>
 </servlet>
 
 <servlet-mapping>
 <servlet-name>Vaadin Application</servlet-name>
 <url-pattern>/VAADIN/*</url-pattern>
 </servlet-mapping>

  <distributable/>
    <error-page>
    <error-code>404</error-code>
    <location>/error.html</location>
  </error-page>
    <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error.html</location>
  </error-page>
</web-app>

struts.xml sample for Struts2


<struts>

 <constant name="struts.multipart.saveDir" value="/mydomain/myapp/website/temp/"/> 
 
 <constant name="struts.devMode" value="false" />
 
 <constant name="struts.enable.SlashesInActionNames" value="true"/>
 <constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/>
 <constant name="struts.action.extension" value="action,,mpg,mp3,gif,png,jpeg,jpg,bmp" />
 <constant name="struts.multipart.maxSize" value="10000000" />
 <!-- struts2 ssl plugin -->
 <constant name="struts2.sslplugin.httpPort" value="80"/>
 <constant name="struts2.sslplugin.httpsPort" value="443"/>
 <constant name="struts2.sslplugin.annotations" value="true"/>
 
 <package name="default" extends="struts-default">
  
  <interceptors>
   <interceptor name="clientTypeInterceptor" class="net.mydomain.myapp.interceptor.ClientTypeInterceptor"></interceptor>
   <interceptor name="ajaxInterceptor" class="net.mydomain.myapp.interceptor.AjaxInterceptor"></interceptor>
   <interceptor name="accessInterceptor" class="net.mydomain.myapp.misc.AccessInterceptor"></interceptor>
   <interceptor name="sslInterceptor"  class="net.mydomain.myapp.ssl.SSLInterceptor"/>
   
   
   <interceptor-stack name="myAjaxStack">
    <interceptor-ref name="ajaxInterceptor"/>
   </interceptor-stack>
   
   <interceptor-stack name="myDefaultStack">
    <!-- remove the following line to work with localhost -->
    <interceptor-ref name="sslInterceptor"/>
    <interceptor-ref name="clientTypeInterceptor">
     <param name="interceptorName">mobileClientInteceptor</param>              
    </interceptor-ref> 
    <interceptor-ref name="defaultStack"/> 
   </interceptor-stack>
   
   <!--  accessStack is same as myDefaultStack with  the accessInterceptor to be used by login methods -->
   <interceptor-stack name="accessStack">
    <!-- remove the following line to work with localhost -->
    <interceptor-ref name="accessInterceptor"/>
    <interceptor-ref name="sslInterceptor"/>
    <interceptor-ref name="clientTypeInterceptor">
     <param name="interceptorName">mobileClientInteceptor</param>              
    </interceptor-ref> 
    <interceptor-ref name="defaultStack"/> 
   </interceptor-stack>
     </interceptors>

  <default-interceptor-ref name="myDefaultStack"/> 
   
  <action name="clientEvent" class="net.mydomain.myapp.controller.EventController" method="clientEvent" >
   <interceptor-ref name="defaultStack"/>
   <result type="stream" name="success">
              <param name="contentType">text/plain</param>
              <param name="inputName">inputStream</param>
              <param name="contentDisposition">filename="response.txt"</param>              
            </result>
  </action>
</package>
<package name="json" namespace="/" extends="json-default">
          <action name="updateVoiceRate" 
        class="net.mydomain.myapp.controller.SettingsController" method="saveVoiceRate">
           <result type="json" name="success">
            <param name="root">jsonObject</param>
           </result>
          </action>


</package>

SSL Interceptor for Struts2

Works with SSLInterceptor plugin for Struts2
Configuration in the struts.xml
 
RequestUtil.java
package mycom.myapp.ssl;

import javax.servlet.http.HttpServletRequest;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;


public class RequestUtil {
    public static String buildQueryString(HttpServletRequest request) {
        // add query string, if any
        String queryString = request.getQueryString();
        StringBuffer finalQs = new StringBuffer();

        if (queryString != null && queryString.length() != 0) {
            finalQs.append(queryString);
        } else {
            queryString = RequestUtil.getRequestParameters(request);
            if (queryString != null && queryString.length() != 0) {
                finalQs.append(queryString);
            }
        }

        return finalQs.length()== 0 ? null : finalQs.toString();

    }


    public static String getRequestParameters(HttpServletRequest aRequest) {

        return createQueryStringFromMap(aRequest.getParameterMap(), "&", aRequest).toString();
    }


    public static StringBuffer createQueryStringFromMap(Map m, String ampersand, HttpServletRequest req) {
        StringBuffer aReturn = new StringBuffer("");
        Set aEntryS = m.entrySet();
        Iterator aEntryI = aEntryS.iterator();
        while (aEntryI.hasNext()) {
            Map.Entry aEntry = (Map.Entry) aEntryI.next();
            Object value = aEntry.getValue();
            String[] aValues = new String[1];
            if (value == null) {
                aValues[0] = "";
            } else if (value instanceof List) { // Work around for Weblogic 6.1sp1
                List aList = (List) value;
                aValues = (String[]) aList.toArray(new String[aList.size()]);
            } else if (value instanceof String) {  // Single value from Struts tags
                aValues[0] = (String) value;
            } else { // String array, the standard returned from request.getParameterMap()
                aValues = (String[]) value;  // This is the standard
            }
            for (int i = 0; i < aValues.length; i++) {

                append(aEntry.getKey(), aValues[i], aReturn, ampersand, req);
            }
        }
        return aReturn;
    }

    private static StringBuffer append(Object key, Object value, StringBuffer queryString, String ampersand, HttpServletRequest req) {

        if (queryString.length() > 0) {
            queryString.append(ampersand);
        }

        // NTS: remove URLEncoder - causes symbols to be stored as hex values
        queryString.append(key.toString());
        queryString.append("=");
        queryString.append(value.toString());

        return queryString;
    }


}

Secured.java

package mycom.myapp.ssl;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
//@Target(METHOD)
public @interface Secured { }

SSLInterceptor.java
package mycom.myapp.ssl;
//http://code.google.com/p/struts2-ssl-plugin/wiki/HowToUse
//Java API imports
import java.lang.reflect.Method;
import java.net.URI;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.struts2.StrutsStatics;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

//Commons API imports
//Struts API imports
public class SSLInterceptor extends AbstractInterceptor {
 private static final long serialVersionUID = 1L;
 private static final Logger logger = Logger.getLogger(AjaxInterceptor.class); 

  private String httpsPort;
  private String httpPort;
  private boolean useAnnotations = true;

  /**
   * Defaults for HTTP and HTTPS ports.  Can be overridden in as a interceptor parm in config file.
   */
  final static int HTTP_PORT = 8080;
  final static int HTTPS_PORT = 8443;

  final static String HTTP_GET = "GET";
  final static String HTTP_POST = "POST";
  final static String SCHEME_HTTP = "http";
  final static String SCHEME_HTTPS = "https";

  /** Creates a new instance of SSLInterceptor */
  public SSLInterceptor() {
      super();
      logger.info ("Intializing SSLInterceptor");
  }

  /**
   * Redirect to SSL or non-SSL version of page as indicated by the presence (or absence) of the
   *  @Secure annotation on the action class.
   */
  public String intercept(ActionInvocation invocation) throws Exception {

      // initialize request and response
      final ActionContext context = invocation.getInvocationContext ();
      final HttpServletRequest request =
          (HttpServletRequest) context.get(StrutsStatics.HTTP_REQUEST);
      final HttpServletResponse response =
          (HttpServletResponse) context.get(StrutsStatics.HTTP_RESPONSE);

      //  add bypass for file uploads
      if (isFileUploadRequest(request)) {
       return invocation.invoke();
      }
      
      //add by pass for session tokens
      String queryString = RequestUtil.buildQueryString(request);
      if (queryString != null && (queryString.toLowerCase().indexOf("&token=") != -1 || queryString.toLowerCase().indexOf("token=") != -1)) {
       return invocation.invoke();
      }
      
      // check scheme
      String scheme = request.getScheme().toLowerCase();

      // check method
      String method = request.getMethod().toUpperCase();


      // if useAnnotations is true check for the annotaion marker in the class level or method level
      // else make every request secure.
      // If the action class/method uses the Secured marker annotation, then see if we need to
      // redirect to the SSL protected version of this page

      Object action = invocation.getAction();
      Method method2 = getActionMethod(action.getClass(), invocation.getProxy().getMethod());

      boolean flg=false;
      
      if (request.getSession().getAttribute("requestUri") != null && request.getRequestURI().equals((String)request.getSession().getAttribute("requestUri"))) {
       //we are coming for 2nd time
    request.getSession().setAttribute("requestUri", null);
    flg=true;
   } else {
    request.getSession().setAttribute("requestUri", request.getRequestURI());
    
   }
      if (request.getSession().getAttribute("switchToHttp") != null) {
       request.getSession().setAttribute("switchToHttp", null);
       return invocation.invoke();
      }
      // If the protocols are the same allow to pass without a redirect
      // NOTE: if statements are seperated but doesn't need to be
      //       think it might be easier to read as separate if else statements
      // If https
      // Else if http
      if ((action.getClass().isAnnotationPresent(Secured.class) ||
           method2.isAnnotationPresent(Secured.class) &&
           referer.toLowerCase().startsWith(SCHEME_HTTPS.toLowerCase()))) {
       return invocation.invoke();
      }
      else if (!method2.isAnnotationPresent(Secured.class) &&
               referer.toLowerCase().startsWith(SCHEME_HTTP.toLowerCase() + "://")) {
       return invocation.invoke();
      }
      
      if ( flg ) {
       //we are here 2nd time with http scheme
          if ( (HTTP_GET.equals(method) || HTTP_POST.equals(method)) && SCHEME_HTTP.equals(scheme)){

           if ((!isUseAnnotations() || action.getClass().isAnnotationPresent(Secured.class) || method2.isAnnotationPresent(Secured.class) )) {
            return invocation.invoke();
           }
              // initialize https port
              int httpsPort = getHttpPort() == null? HTTP_PORT : Integer.parseInt(getHttpPort());

              URI uri = new URI(SCHEME_HTTP, null, request.getServerName(),
                  httpsPort, response.encodeRedirectURL(request.getRequestURI()),
                  queryString, null);

              logger.info("Going to SSL mode , redirecting to " + uri.toString());

              response.sendRedirect(uri.toString());
              return null;
          }

      } else if (!isUseAnnotations() || action.getClass().isAnnotationPresent(Secured.class) || method2.isAnnotationPresent(Secured.class) ){
      
       
          if ( (HTTP_GET.equals(method) || HTTP_POST.equals(method)) && SCHEME_HTTP.equals(scheme)){

              // initialize https port
              int httpsPort = getHttpsPort() == null? HTTPS_PORT : Integer.parseInt(getHttpsPort());

              URI uri = new URI(SCHEME_HTTPS, null, request.getServerName(),
                  httpsPort, response.encodeRedirectURL(request.getRequestURI()),
                  queryString, null);

              logger.info("Going to SSL mode, redirecting to " + uri.toString());

              response.sendRedirect(uri.toString());
              return null;
          }
      }  else{

          if ((HTTP_GET.equals(method) || HTTP_POST.equals(method)) && SCHEME_HTTP.equals(scheme)){//used to be SCHEME_HTTPS

              // initialize http port
              int httpPort = getHttpPort() == null? HTTP_PORT : Integer.parseInt(getHttpPort());

              URI uri = new URI(SCHEME_HTTP, null, request.getServerName(),
                  httpPort, response.encodeRedirectURL(request.getRequestURI()),
                  queryString, null);

              logger.info("Going to non-SSL mode, redirecting to " + uri.toString());
              request.getSession().setAttribute("switchToHttp", "true");
              response.sendRedirect(uri.toString());
              return null;
          }
          
      }

      return invocation.invoke();
  }
  
  // NTS
  private boolean isFileUploadRequest(HttpServletRequest request) {
   return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/form-data") 
     && request.getMethod() != null && request.getMethod().equalsIgnoreCase("POST");
  }
  
  // FIXME: This is copied from DefaultActionInvocation but should be exposed through the interface
  protected Method getActionMethod(Class actionClass, String methodName) throws NoSuchMethodException {
      Method method;
      try {
          method = actionClass.getMethod(methodName, new Class[0]);
      } catch (NoSuchMethodException e) {
          // hmm -- OK, try doXxx instead
          try {
              String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
              method = actionClass.getMethod(altMethodName, new Class[0]);
          } catch (NoSuchMethodException e1) {
              // throw the original one
              throw e;
          }
      }
      return method;
  }


  public String getHttpsPort() {
      return httpsPort;
  }

  @Inject(value="struts2.sslplugin.httpsPort",required = false)
  public void setHttpsPort(String httpsPort) {
      this.httpsPort = httpsPort;
  }

  public String getHttpPort() {
      return httpPort;
  }

  @Inject(value = "struts2.sslplugin.httpPort", required = false)
  public void setHttpPort(String httpPort) {
      this.httpPort = httpPort;
  }

  public boolean isUseAnnotations() {
      return useAnnotations;
  }

  public void setUseAnnotations(boolean useAnnotations) {
      this.useAnnotations = useAnnotations;
  }

  @Inject(value = "struts2.sslplugin.annotations", required = false)
  public void setAnnotations(String annotations) {
      if (annotations==null) {
          annotations = "true";
      }
      this.useAnnotations = new Boolean(annotations).booleanValue();
  }

}

Java code for Login with Facebook


package net.mydomain.myapp.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;

import net.mydomain.myapp.controller.helper.FacebookDaoImpl;
import net.mydomain.myapp.domain.Account;
import net.mydomain.myapp.domain.AccountPending;
import net.mydomain.myapp.domain.Gender;
import net.mydomain.myapp.domain.Status;
import net.mydomain.myapp.domain.dao.AccountDaoImpl;
import net.mydomain.myapp.misc.myappProperties;
import net.mydomain.myapp.util.HibernateUtil;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.google.gson.Gson;
import com.opensymphony.xwork2.ActionContext;

public class FacebookController extends MyappActionSupport {
 private static final long serialVersionUID = 1L;
 private String code;
 private String state;
 private String location;
 private String FACEBOOK = "facebook"; // Facebook login
 private String HOME = "home";    //user clicked on cancel...show home page
 private String SIGNUP = "signup";  //show registration

 public String getCode() {
  return code;
 }

 public void setCode(String code) {
  this.code = code;
 }

 public String getState() {
  return state;
 }

 public void setState(String state) {
  this.state = state;
 }

 public String getLocation() {
  return location;
 }

 public void setLocation(String location) {
  this.location = location;
 }

 public String fbCallBack() {
  logger.info("code=" + code + " state=" + state);

  // Is this from facebook?
  clearHttpSession();
  
  // Validation
  if (StringUtils.isEmpty(state)) {
   logger.info("Facebook controller: Facebook callback: no state");
   addActionError("Failed facebook login");
   return HOME;
  }
  if (StringUtils.isEmpty(code)) {
   logger.info("Facebook controller: Facebook callback: no code");
   addActionError("Failed facebook login");
   return HOME;
  }
  
  code = URLEncoder.encode(code);
  String fbClientId;
  String fbClientPassword;
  String fbRedirectUrl;
  Session session = null;
  Transaction tran = null;
  try {
   fbClientId = myappProperties.faceBookClientId();
   fbClientPassword = myappProperties.faceBookClientPassword();
   fbRedirectUrl = myappProperties.baseUrl() + "fbCallBack";

   // obtain the access token
   URL url = new URL(
     "https://graph.facebook.com/oauth/access_token?client_id="
       + fbClientId + "&code=" + code + "&type=web_server"
       + "&client_secret=" + fbClientPassword  
       + "&redirect_uri=" + fbRedirectUrl);
   HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
   String resp = getUrlContent(con);
   logger.info("response=" + resp);
   if (StringUtils.isEmpty(resp)) {
    addActionError("Failed facebook login");
    return HOME;
   }
   String[] resp_arr = resp.split("=");
   String accessToken = resp_arr[1];

   // obtain user profile....email, username, etc.
   url = new URL("https://graph.facebook.com/me?access_token="
     + accessToken);
   con = (HttpsURLConnection) url.openConnection();
   resp = getUrlContent(con);
   logger.info("response=" + resp);
   Gson gson = new Gson();
   FacebookDaoImpl fb = gson.fromJson(resp, FacebookDaoImpl.class);

   // check if  account exists for this user
   session = HibernateUtil.getCurremydomainession();
   tran = session.beginTransaction();
   if (fb.getEmail() == null) {
    logger.info("Permission to access email on Facebook is disabled");
    addActionError("You have not enabled permission to access email on Facebook");
    return ERROR;
   }
   Account anAccount = new AccountDaoImpl().getByEmail(fb.getEmail());
   
   if (anAccount == null) {
    // send them to registration
    AccountPending ap = new AccountPending();
    ap.setFirstName(fb.getFirstName());
    ap.setLastName(fb.getLastName());
    ap.setEmail(fb.getEmail().toLowerCase());
    ap.setPassword(null);
    ap.setCreateUsername(HibernateUtil.webUpdateUserName);
    ap.setTnCodeVerified(false);
    ap.setGender(fb.getGender() == null ? Gender.M : (fb.getGender().toUpperCase().startsWith("M") ? Gender.M : Gender.F));
    session.save(ap); 
    tran.commit();
    setSessionIdAccountPending(ap.getIdAccountPending());
    return SIGNUP;
   }
   if (anAccount.getStatusId() == Status.DEACTIVATED) {
    logger.info("account deactivated");
    addActionError("Your account has been deactivated.");
    return ERROR;
   }
   anAccount.setFbAccessToken(accessToken.substring(0,
     accessToken.indexOf("&expires")));
   anAccount.setLastLoginTimestamp(new Date());
   session.update(anAccount);
   tran.commit();
   populateHttpSession(anAccount);
  } catch (Exception e) {
   if (tran != null) 
    tran.rollback();
   logger.error("Exception ", e);
  } finally {
   if (session != null)
    session.close();
  }
  return HOME;
 }

 private String getUrlContent(HttpsURLConnection con) {
  String output = "";
  if (con != null) {
   try {
    BufferedReader br = new BufferedReader(new InputStreamReader(
      con.getInputStream()));
    String input;
    while ((input = br.readLine()) != null) {
     output += input;
     ;
    }
    br.close();
   } catch (IOException e) {
    logger.warn("Failed to get url content", e);
   } catch (Exception e) {
    logger.error("Error getting url content", e);
   }
  }
  return output;
 }
 private void setSessionIdAccountPending(Long idAccountPending) {
  Map httpSession = ActionContext.getContext().getSession();
  httpSession.put("idAccountPending", idAccountPending);
 }
 public String fbCall()
 { 
  String fbRedirectUrl = "";
  String fbClientId = "";
  if (StringUtils.isEmpty(state))
   state="test";
  logger.info("state=" + state);
  try{
   fbRedirectUrl = myappProperties.baseUrl() + "fbCallBack";
   fbClientId = myappProperties.faceBookClientId();
   
   location = "https://graph.facebook.com/oauth/authorize?client_id=" + fbClientId +
                                                        "&type=web_server" +
                                                        "&display=popup" +
                                                        "&redirect_uri=" + fbRedirectUrl +
                                                        "&response_type=token" +
                                                        "&state=" + state +
                                                        "&scope=email";
  } catch (Exception e) {
   logger.error("Exception in fbCall ", e);
  }
  return FACEBOOK;
 }
 private void clearHttpSession() {
  Map httpSession = ActionContext.getContext().getSession();
  httpSession.clear();
 }
}
FacebookDaoImpl.java
package net.mydomain.myapp.controller.helper;


public class FacebookDaoImpl  {

 private String id;
 private String name;
 private String first_name;
 private String last_name;
 private String link;
 private String username;
 private String gender;
 private String locale;
 private String type;
 private String picture;
 private String email;

 public String getId() {
  return id;
 }

 public String getName() {
  return name;
 }

 public String getFirstName() {
  return first_name;
 }

 public String getLastName() {
  return last_name;
 }

 public String getLink() {
  return link;
 }

 public String getUsername() {
  return username;
 }

 public String getGender() {
  return gender;
 }

 public String getLocale() {
  return locale;
 }

 public String getType() {
  return type;
 }

 public String getPicture() {
  return picture;
 }

 public String getEmail() {
  return email;
 }

}
Struts.xml
<action name="fbCall" class="net.mydomain.myapp.controller.FacebookController" method="fbCall">
   <result name="facebook" type="redirect">
    <param name="location">${location}</param>
   </result>
   <result name="facebook_mobile" type="redirect">
    <param name="location">${location}</param>
   </result>
  </action>
  <action name="fbCallBack" class="net.mydomain.myapp.controller.FacebookController" method="fbCallBack">
   <result name="home" type="redirectAction">
    <param name="actionName">home</param>
   </result>
   <result name="home_mobile" type="redirectAction">
    <param name="actionName">home</param>
   </result>
   <result name="signup">terminatingNumber.jsp</result>
   <result name="signup_mobile">mobile/tnEnteredM.jsp</result>
   <result name="error">error.jsp</result>
   <result name="error_mobile">mobile/error.jsp</result>
  </action>

Java ESL Freeswitch to bridge 2 calls


Jars required:
  • hamcrest-all-1.1.jar
  • junit.jar
  • netty-3.6.3.Final.jar
  • org.freeswitch.esl.client-0.9.3-SNAPSHOT.jar
package net.freeswitch;
/*
 * Copyright 2010 david varnes.
 *
 * Licensed under the Apache License, version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.Map.Entry;
import java.util.UUID;

import org.freeswitch.esl.client.IEslEventListener;
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
import org.freeswitch.esl.client.transport.CommandResponse;
import org.freeswitch.esl.client.transport.SendMsg;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
import org.freeswitch.esl.client.transport.message.EslMessage;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClientTest
{
    private final Logger log = LoggerFactory.getLogger( this.getClass() );

    private String host = "127.0.0.1";
    private int port = 8021;
    private String password = "ClueCon"; 
        
    @Test
    public void do_connect() throws InterruptedException
    {
        Client client = new Client();
     
        client.addEventListener( new IEslEventListener()
        {
            public void eventReceived( EslEvent event )
            {
                log.info( "Event received [{}]", event );
            }
            public void backgroundJobResultReceived( EslEvent event )
            {
                log.info( "Background job result received [{}]", event );
            }
            
        } );
        
        log.info( "Client connecting .." );
        try
        {
            client.connect( host, port, password, 20 );
        }
        catch ( InboundConnectionFailure e )
        {
            log.error( "Connect failed", e );
            return;
        }
        log.info( "Client connected .." );
        
//      client.setEventSubscriptions( "plain", "heartbeat CHANNEL_CREATE CHANNEL_DESTROY BACKGROUND_JOB" );
        client.setEventSubscriptions( "plain", "all" );
        client.addEventFilter( "Event-Name", "heartbeat" );
        client.cancelEventSubscriptions();
        client.setEventSubscriptions( "plain", "all" );
        client.addEventFilter( "Event-Name", "heartbeat" );
        client.addEventFilter( "Event-Name", "channel_create" );
        client.addEventFilter( "Event-Name", "background_job" );
        client.sendSyncApiCommand( "echo", "Foo foo bar" );
     String uuid1=UUID.randomUUID().toString();
     String uuid2=UUID.randomUUID().toString();
     EslMessage resp=client.sendSyncApiCommand("originate ", "{origination_uuid=" + uuid1 + "}sofia/gateway/mygateway/OUTBOUND+18185551212 1004 park" );
     log.info( "Response to 'park1': [{}]", resp );
        for ( Entry header : resp.getHeaders().entrySet() )
        {
            log.info( " * header [{}]", header );
        }
        for ( String bodyLine : resp.getBodyLines() )
        {
            log.info( " * body [{}]", bodyLine );
        }
        
        SendMsg playMsg = new SendMsg(uuid1);
        playMsg.addCallCommand( "execute" );
        playMsg.addExecuteAppName( "speak" );
        playMsg.addExecuteAppArg("cepstral|callie|welcome to java. Welcome to make call");
        CommandResponse cmdresp = client.sendMessage(playMsg);
        
        resp=client.sendSyncApiCommand("originate ", "{origination_uuid=" + uuid2 + "}sofia/gateway/mygateway/OUTBOUND+17325551212 1004 park" );
     log.info( "Response to 'park2': [{}]", resp );
        for ( Entry header : resp.getHeaders().entrySet() )
        {
            log.info( " * header [{}]", header );
        }
        for ( String bodyLine : resp.getBodyLines() )
        {
            log.info( " * body [{}]", bodyLine );
        }
        resp=client.sendSyncApiCommand("uuid_bridge ", uuid1 + " " + uuid2 );
     log.info( "Response to 'uuid_bridge': [{}]", resp );
        for ( Entry header : resp.getHeaders().entrySet() )
        {
            log.info( " * header [{}]", header );
        }
        for ( String bodyLine : resp.getBodyLines() )
        {
            log.info( " * body [{}]", bodyLine );
        }
     //CommandResponse cmd=CommandResponse( bMsg.toString(), resp);
        //client.sendSyncApiCommand("originate", "sofia/gateway/mygateway/OUTBOUND+17325551212 park");
        //client.sendSyncApiCommand("bridge", "sofia/gateway/mygateway/OUTBOUND+18185551212 1004 park" );
//        client.sendSyncApiCommand( "sofia status", "" );
        String jobId = client.sendAsyncApiCommand( "status", "" );
        log.info( "Job id [{}] for [status]", jobId );
        client.sendSyncApiCommand( "version", "" );
//        client.sendAsyncApiCommand( "status", "" );
//        client.sendSyncApiCommand( "sofia status", "" );
//        client.sendAsyncApiCommand( "status", "" );
        EslMessage response = client.sendSyncApiCommand( "sofia status", "" );
        log.info( "sofia status = [{}]", response.getBodyLines().get( 3 ) );
        
        // wait to see the heartbeat events arrive
        Thread.sleep( 40000 );
        client.close();
    }

    @Test
    public void do_multi_connects() throws InterruptedException
    {
        Client client = new Client();
        
        log.info( "Client connecting .." );
        try
        {
            client.connect( host, port, password, 2 );
        }
        catch ( InboundConnectionFailure e )
        {
            log.error( "Connect failed", e );
            return;
        }
        log.info( "Client connected .." );
        
        log.info( "Client connecting .." );
        try
        {
            client.connect( host, port, password, 2 );
        }
        catch ( InboundConnectionFailure e )
        {
            log.error( "Connect failed", e );
            return;
        }
        log.info( "Client connected .." );
        
        client.close();
    }
    
    @Test
    public void sofia_contact()
    {
        Client client = new Client();
        try
        {
            client.connect( host, port, password, 2 );
        }
        catch ( InboundConnectionFailure e )
        {
            log.error( "Connect failed", e );
            return;
        }
        
        EslMessage response = client.sendSyncApiCommand( "sofia_contact", "internal/102@192.xxx.xxx.xxx" );

        log.info( "Response to 'sofia_contact': [{}]", response );
        for ( Entry header : response.getHeaders().entrySet() )
        {
            log.info( " * header [{}]", header );
        }
        for ( String bodyLine : response.getBodyLines() )
        {
            log.info( " * body [{}]", bodyLine );
        }
        client.close();
    }
}
Client.java
package net.freeswitch;

/*
 * Copyright 2010 david varnes.
 *
 * Licensed under the Apache License, version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.freeswitch.esl.client.IEslEventListener;
import org.freeswitch.esl.client.inbound.InboundClientHandler;
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
import org.freeswitch.esl.client.inbound.InboundPipelineFactory;
import org.freeswitch.esl.client.internal.IEslProtocolListener;
import org.freeswitch.esl.client.transport.CommandResponse;
import org.freeswitch.esl.client.transport.SendMsg;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.freeswitch.esl.client.transport.message.EslMessage;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Entry point to connect to a running FreeSWITCH Event Socket Library module, as a client.
 * * This class provides what the FreeSWITCH documentation refers to as an 'Inbound' connection
 * to the Event Socket module. That is, with reference to the socket listening on the FreeSWITCH
 * server, this client occurs as an inbound connection to the server.
 * * See http://wiki.freeswitch.org/wiki/Mod_event_socket
 * 
 * @author  david varnes
 */
public class Client
{
    private final Logger log = LoggerFactory.getLogger( this.getClass() );
    
    private final List eventListeners = new CopyOnWriteArrayList();
    private final Executor eventListenerExecutor = Executors.newSingleThreadExecutor( 
        new ThreadFactory()
        {
            AtomicInteger threadNumber = new AtomicInteger( 1 );
            public Thread newThread( Runnable r )
            {
                return new Thread( r, "EslEventNotifier-" + threadNumber.getAndIncrement() );
            }
        });
    private final Executor backgroundJobListenerExecutor = Executors.newSingleThreadExecutor(
        new ThreadFactory()
        {
            AtomicInteger threadNumber = new AtomicInteger( 1 );
            public Thread newThread( Runnable r )
            {
                return new Thread( r, "EslBackgroundJobNotifier-" + threadNumber.getAndIncrement() );
            }
        });
    
    private AtomicBoolean authenticatorResponded = new AtomicBoolean( false );
    private boolean authenticated;
    private CommandResponse authenticationResponse;
    private Channel channel;
    
    public boolean canSend()
    {
        return channel != null && channel.isConnected() && authenticated; 
    }
    
    public void addEventListener( IEslEventListener listener )
    {
        if ( listener != null )
        {
            eventListeners.add( listener );
        }
    }

    /**
     * Attempt to establish an authenticated connection to the nominated FreeSWITCH ESL server socket.
     * This call will block, waiting for an authentication handshake to occur, or timeout after the
     * supplied number of seconds.  
     *  
     * @param host can be either ip address or hostname
     * @param port tcp port that server socket is listening on (set in event_socket_conf.xml)
     * @param password server event socket is expecting (set in event_socket_conf.xml) 
     * @param timeoutSeconds number of seconds to wait for the server socket before aborting
     */
    public void connect( String host, int port, String password, int timeoutSeconds ) throws InboundConnectionFailure
    {
        // If already connected, disconnect first
        if ( canSend() )
        {
            close();
        }
        
        // Configure this client
        ClientBootstrap bootstrap = new ClientBootstrap(
            new NioClientSocketChannelFactory( 
                Executors.newCachedThreadPool(), 
                Executors.newCachedThreadPool() ) ); 
        
        // Add ESL handler and factory
        InboundClientHandler handler = new InboundClientHandler( password, protocolListener );
        bootstrap.setPipelineFactory( new InboundPipelineFactory( handler ) );
        
        // Attempt connection
        ChannelFuture future = bootstrap.connect( new InetSocketAddress( host, port ) );
        
        // Wait till attempt succeeds, fails or timeouts
        if ( ! future.awaitUninterruptibly( timeoutSeconds, TimeUnit.SECONDS ) )
        {
            throw new InboundConnectionFailure( "Timeout connecting to " + host + ":" + port );
        }
        // Did not timeout 
        channel = future.getChannel();
        // But may have failed anyway
        if ( !future.isSuccess() )
        {
            log.warn( "Failed to connect to [{}:{}]", host, port );
            log.warn( "  * reason: {}", future.getCause() );
            
            channel = null;
            bootstrap.releaseExternalResources();
            
            throw new InboundConnectionFailure( "Could not connect to " + host + ":" + port, future.getCause() );
        }
        
        //  Wait for the authentication handshake to call back
        while ( ! authenticatorResponded.get() )
        {
            try
            {
                Thread.sleep( 250 );
            } 
            catch ( InterruptedException e )
            {
                // ignore
            }
        }
        
        if ( ! authenticated )
        {
            throw new InboundConnectionFailure( "Authentication failed: " + authenticationResponse.getReplyText() );
        }
    }
    
    /**
     * Sends a FreeSWITCH API command to the server and blocks, waiting for an immediate response from the 
     * server.
     * * The outcome of the command from the server is retured in an {@link EslMessage} object.
     * 
     * @param command API command to send
     * @param arg command arguments
     * @return an {@link EslMessage} containing command results
     */
    public EslMessage sendSyncApiCommand( String command, String arg )
    {
        checkConnected();
        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
        StringBuilder sb = new StringBuilder();
        if ( command != null && !command.isEmpty() )
        {
            sb.append( "api " );
            sb.append( command );
        }
        if ( arg != null && !arg.isEmpty() )
        {
            sb.append( arg );
        }

        return handler.sendSyncSingleLineCommand( channel, sb.toString() );
    }
    
    /**
     * Submit a FreeSWITCH API command to the server to be executed in background mode. A synchronous 
     * response from the server provides a UUID to identify the job execution results. When the server
     * has completed the job execution it fires a BACKGROUND_JOB Event with the execution results.* Note that this Client must be subscribed in the normal way to BACKGOUND_JOB Events, in order to 
     * receive this event.
     *     
     * @param command API command to send
     * @param arg command arguments
     * @return String Job-UUID that the server will tag result event with.
     */
    public String sendAsyncApiCommand( String command, String arg )
    {
        checkConnected();
        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
        StringBuilder sb = new StringBuilder();
        if ( command != null && !command.isEmpty() )
        {
            sb.append( "bgapi " );
            sb.append( command );
        }
        if ( arg != null && !arg.isEmpty() )
        {
            sb.append( ' ' );
            sb.append( arg );
        }
        
        return handler.sendAsyncCommand( channel, sb.toString() );
    }
    
    /**
     * Set the current event subscription for this connection to the server.  Examples of the events 
     * argument are:
     * 
     *   ALL
     *   CHANNEL_CREATE CHANNEL_DESTROY HEARTBEAT
     *   CUSTOM conference::maintenance
     *   CHANNEL_CREATE CHANNEL_DESTROY CUSTOM conference::maintenance sofia::register sofia::expire
     * 
* Subsequent calls to this method replaces any previous subscriptions that were set. * * Note: current implementation can only process 'plain' events. * * @param format can be { plain | xml } * @param events { all | space separated list of events } * @return a {@link CommandResponse} with the server's response. */ public CommandResponse setEventSubscriptions( String format, String events ) { // temporary hack if ( ! format.equals( "plain" ) ) { throw new IllegalStateException( "Only 'plain' event format is supported at present" ); } checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); StringBuilder sb = new StringBuilder(); if ( format != null && !format.isEmpty() ) { sb.append( "event " ); sb.append( format ); } if ( events != null && !events.isEmpty() ) { sb.append( ' ' ); sb.append( events ); } EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() ); return new CommandResponse( sb.toString(), response ); } /** * Cancel any existing event subscription. * * @return a {@link CommandResponse} with the server's response. */ public CommandResponse cancelEventSubscriptions() { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); EslMessage response = handler.sendSyncSingleLineCommand( channel, "noevents" ); return new CommandResponse( "noevents", response ); } /** * Add an event filter to the current set of event filters on this connection. Any of the event headers * can be used as a filter. * * Note that event filters follow 'filter-in' semantics. That is, when a filter is applied * only the filtered values will be received. Multiple filters can be added to the current * connection. * * Example filters: *
     *    eventHeader        valueToFilter
     *    ----------------------------------
     *    Event-Name         CHANNEL_EXECUTE
     *    Channel-State      CS_NEW
     * 
* * @param eventHeader to filter on * @param valueToFilter the value to match * @return a {@link CommandResponse} with the server's response. */ public CommandResponse addEventFilter( String eventHeader, String valueToFilter ) { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); StringBuilder sb = new StringBuilder(); if ( eventHeader != null && !eventHeader.isEmpty() ) { sb.append( "filter " ); sb.append( eventHeader ); } if ( valueToFilter != null && !valueToFilter.isEmpty() ) { sb.append( ' ' ); sb.append( valueToFilter ); } EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() ); return new CommandResponse( sb.toString(), response ); } /** * Delete an event filter from the current set of event filters on this connection. See * {@link Client.addEventFilter} * * @param eventHeader to remove * @param valueToFilter to remove * @return a {@link CommandResponse} with the server's response. */ public CommandResponse deleteEventFilter( String eventHeader, String valueToFilter ) { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); StringBuilder sb = new StringBuilder(); if ( eventHeader != null && !eventHeader.isEmpty() ) { sb.append( "filter delete " ); sb.append( eventHeader ); } if ( valueToFilter != null && !valueToFilter.isEmpty() ) { sb.append( ' ' ); sb.append( valueToFilter ); } EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() ); return new CommandResponse( sb.toString(), response ); } /** * Send a {@link SendMsg} command to FreeSWITCH. This client requires that the {@link SendMsg} * has a call UUID parameter. * * @param sendMsg a {@link SendMsg} with call UUID * @return a {@link CommandResponse} with the server's response. */ public CommandResponse sendMessage( SendMsg sendMsg ) { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); EslMessage response = handler.sendSyncMultiLineCommand( channel, sendMsg.getMsgLines() ); return new CommandResponse( sendMsg.toString(), response ); } /** * Enable log output. * * @param level using the same values as in console.conf * @return a {@link CommandResponse} with the server's response. */ public CommandResponse setLoggingLevel( String level ) { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); StringBuilder sb = new StringBuilder(); if ( level != null && !level.isEmpty() ) { sb.append( "log " ); sb.append( level ); } EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() ); return new CommandResponse( sb.toString(), response ); } /** * Disable any logging previously enabled with setLogLevel(). * * @return a {@link CommandResponse} with the server's response. */ public CommandResponse cancelLogging() { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); EslMessage response = handler.sendSyncSingleLineCommand( channel, "nolog" ); return new CommandResponse( "nolog", response ); } /** * Close the socket connection * * @return a {@link CommandResponse} with the server's response. */ public CommandResponse close() { checkConnected(); InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast(); EslMessage response = handler.sendSyncSingleLineCommand( channel, "exit" ); return new CommandResponse( "exit", response ); } /* * Internal observer of the ESL protocol */ private final IEslProtocolListener protocolListener = new IEslProtocolListener() { public void authResponseReceived( CommandResponse response ) { authenticatorResponded.set( true ); authenticated = response.isOk(); authenticationResponse = response; log.debug( "Auth response success={}, message=[{}]", authenticated, response.getReplyText() ); } public void eventReceived( final EslEvent event ) { log.debug( "Event received [{}]", event ); /* * Notify listeners in a different thread in order to: * - not to block the IO threads with potentially long-running listeners * - generally be defensive running other people's code * Use a different worker thread pool for async job results than for event driven * events to keep the latency as low as possible. */ if ( event.getEventName().equals( "BACKGROUND_JOB" ) ) { for ( final IEslEventListener listener : eventListeners ) { backgroundJobListenerExecutor.execute( new Runnable() { public void run() { try { listener.backgroundJobResultReceived( event ); } catch ( Throwable t ) { log.error( "Error caught notifying listener of job result [" + event + ']', t ); } } } ); } } else { for ( final IEslEventListener listener : eventListeners ) { eventListenerExecutor.execute( new Runnable() { public void run() { try { listener.eventReceived( event ); } catch ( Throwable t ) { log.error( "Error caught notifying listener of event [" + event + ']', t ); } } } ); } } } public void disconnected() { log.info( "Disconnected .." ); } }; private void checkConnected() { if ( ! canSend() ) { throw new IllegalStateException( "Not connected to FreeSWITCH Event Socket" ); } } }