package org.jboss.seam.mock;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.el.ELResolver;
import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.mail.internet.MimeMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.transaction.UserTransaction;
import javax.validation.ConstraintViolation;

import org.jboss.seam.Component;
import org.jboss.seam.Seam;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.contexts.FacesLifecycle;
import org.jboss.seam.contexts.ServletLifecycle;
import org.jboss.seam.contexts.TestLifecycle;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Init;
import org.jboss.seam.core.Manager;
import org.jboss.seam.core.Validators;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.faces.Renderer;
import org.jboss.seam.init.Initialization;
import org.jboss.seam.jsf.SeamPhaseListener;
import org.jboss.seam.mail.MailSession;
import org.jboss.seam.pageflow.Pageflow;
import org.jboss.seam.servlet.SeamFilter;
import org.jboss.seam.servlet.ServletSessionMap;
import org.jboss.seam.transaction.Transaction;
import org.jboss.seam.util.Naming;
import org.jboss.seam.util.Reflections;

/**
 * Base class for integration tests for JSF/Seam applications. This class can be
 * extended or referenced directly for integration with various testing
 * frameworks.
 * 
 * @author Gavin King
 * @author <a href="mailto:theute@jboss.org">Thomas Heute</a>
 */
public class AbstractSeamTest
{

   private Application application;
   private ApplicationFactory applicationFactory;
   protected ServletContext servletContext;
   private static SeamPhaseListener phases;
   protected MockHttpSession session;
   private Map<String, Map> conversationViewRootAttributes;
   protected Filter seamFilter;
   
   private static ServletContext realServletContext = null;
   
   static 
   {
      phases = new SeamPhaseListener();
   }

   protected boolean isSessionInvalid()
   {
      return session.isInvalid();
   }

   protected HttpSession getSession()
   {
      return session;
   }

   /**
    * Helper method for resolving components in the test script.
    */
   protected Object getInstance(Class clazz)
   {
      return Component.getInstance(clazz);
   }

   /**
    * Helper method for resolving components in the test script.
    */
   protected Object getInstance(String name)
   {
      return Component.getInstance(name);
   }

   /**
    * Is there a long running conversation associated with the current request?
    */
   protected boolean isLongRunningConversation()
   {
      return Manager.instance().isLongRunningConversation();
   }

   protected String getConversationIdParameter()
   {
      return "conversationId";
   }
   
   /**
    * Search in all contexts
    */
   public Object lookup(String name)
   {
      return Contexts.lookupInStatefulContexts(name);
   }

   public abstract class ComponentTest
   {
      /**
       * Call a method binding
       */
      protected Object invokeMethod(String methodExpression)
      {
         return Expressions.instance().createMethodExpression(methodExpression).invoke();
      }

      /**
       * Evaluate (get) a value binding
       */
      protected Object getValue(String valueExpression)
      {
         return Expressions.instance().createValueExpression(valueExpression).getValue();
      }

      /**
       * Set a value binding
       */
      protected void setValue(String valueExpression, Object value)
      {
         Expressions.instance().createValueExpression(valueExpression).setValue(value);
      }

      protected abstract void testComponents() throws Exception;

      public void run() throws Exception
      {
         TestLifecycle.beginTest(servletContext, new ServletSessionMap(session));
         try
         {
            testComponents();
         }
         finally
         {
            TestLifecycle.endTest();
         }
      }
   }

   /**
    * Request is an abstract superclass for usually anonymous inner classes that
    * test JSF interactions.
    * 
    * @author Gavin King
    */
   abstract class Request
   {
      private String conversationId;
      private String outcome;
      private String action;
      private boolean validationFailed;
      private String viewId;

      private boolean renderResponseBegun;
      private boolean renderResponseComplete;
      private boolean invokeApplicationBegun;
      private boolean invokeApplicationComplete;

      private HttpServletRequest request;
      private HttpServletResponse response;
      private MockFacesContext facesContext;
      private MockExternalContext externalContext;
      private Map<String, Object> pageParameters = new HashMap<String, Object>();

      protected void setPageParameter(String name, Object value)
      {
         pageParameters.put(name, value);
      }

      protected void setParameter(String name, String value)
      {
         getParameters().put(name, new String[] { value });
      }

      protected Map<String, String[]> getParameters()
      {
         return ((MockHttpServletRequest) externalContext.getRequest()).getParameters();
      }

      protected Map<String, String[]> getHeaders()
      {
         return ((MockHttpServletRequest) externalContext.getRequest()).getHeaders();
      }

      /**
       * Override to define the name of the current principal
       * 
       * @return "gavin" by default
       */
      public String getPrincipalName()
      {
         return "gavin";
      }

      /**
       * Override to define the roles assigned to the current principal
       * 
       * @return a Set of all roles by default
       */
      public Set<String> getPrincipalRoles()
      {
         return new AbstractSet<String>()
         {
            @Override
            public boolean contains(Object o)
            {
               return true;
            }

            @Override
            public Iterator<String> iterator()
            {
               throw new UnsupportedOperationException();
            }

            @Override
            public int size()
            {
               throw new UnsupportedOperationException();
            }
         };
      }

      public List<Cookie> getCookies()
      {
         return Collections.EMPTY_LIST;
      }

      /**
       * A script for a JSF interaction with no existing long-running
       * conversation.
       */
      protected Request()
      {
      }

      /**
       * A script for a JSF interaction in the scope of an existing long-running
       * conversation.
       */
      protected Request(String conversationId)
      {
         this.conversationId = conversationId;
      }

      /**
       * Is this a non-faces request? Override if it is.
       * 
       * @return false by default
       */
      protected boolean isGetRequest()
      {
         return false;
      }

      /**
       * The JSF view id of the form that is being submitted or of the page that
       * is being rendered in a non-faces request. (override if you need page
       * actions to be called, and page parameters applied)
       */
      protected String getViewId()
      {
         return viewId;
      }

      protected void setViewId(String viewId)
      {
         this.viewId = viewId;
      }

      /**
       * Override to implement any logic that requires active scopes
       * but must happen before the rest of the JSF lifecycle.
       */ 
      protected void afterRestoreViewPhase() throws Exception
      {
      }

      /**
       * Override to implement the interactions between the JSF page and your
       * components that occurs during the apply request values phase.
       */
      protected void applyRequestValues() throws Exception
      {
      }

      /**
       * Override to implement the interactions between the JSF page and your
       * components that occurs during the process validations phase.
       */
      protected void processValidations() throws Exception
      {
      }

      /**
       * Override to implement the interactions between the JSF page and your
       * components that occurs during the update model values phase.
       */
      protected void updateModelValues() throws Exception
      {
      }

      /**
       * Override to implement the interactions between the JSF page and your
       * components that occurs during the invoke application phase.
       */
      protected void invokeApplication() throws Exception
      {
      }

      /**
       * Set the outcome of the INVOKE_APPLICATION phase
       */
      protected void setOutcome(String outcome)
      {
         this.outcome = outcome;
      }

      /**
       * The outcome of the INVOKE_APPLICATION phase
       */
      protected String getOutcome()
      {
         return outcome;
      }

      /**
       * Get the outcome of the INVOKE_APPLICATION phase
       */
      protected String getInvokeApplicationOutcome()
      {
         return outcome;
      }

      /**
       * Override to implement the interactions between the JSF page and your
       * components that occurs during the render response phase.
       */
      protected void renderResponse() throws Exception
      {
      }

      /**
       * Make some assertions, after the end of the request.
       */
      protected void afterRequest()
      {
      }

      /**
       * Do anything you like, after the start of the request. Especially, set
       * up any request parameters for the request.
       */
      protected void beforeRequest()
      {
      }

      /**
       * Get the view id to be rendered
       * 
       * @return the JSF view id
       */
      protected String getRenderedViewId()
      {
         if (Init.instance().isJbpmInstalled() && Pageflow.instance().isInProcess())
         {
            return Pageflow.instance().getPageViewId();
         }
         else
         {
            // TODO: not working right now, 'cos no mock navigation handler!
            return getFacesContext().getViewRoot().getViewId();
         }
      }

      /**
       * Did a validation failure occur during a call to validate()?
       */
      protected boolean isValidationFailure()
      {
         return validationFailed;
      }

      protected FacesContext getFacesContext()
      {
         return facesContext;
      }

      protected String getConversationId()
      {
         return conversationId;
      }

      /**
       * Evaluate (get) a value binding
       */
      protected Object getValue(String valueExpression)
      {
         return application.evaluateExpressionGet(facesContext, valueExpression, Object.class);
      }

      /**
       * Set a value binding
       */
      protected void setValue(String valueExpression, Object value)
      {
         application.getExpressionFactory().createValueExpression(facesContext.getELContext(), valueExpression, Object.class).setValue(facesContext.getELContext(), value);
      }

      /**
       * Validate the value against model-based constraints return true if the
       * value is valid
       */
      protected boolean validateValue(String valueExpression, Object value)
      {
         ValueExpression ve = application.getExpressionFactory().createValueExpression(facesContext.getELContext(), valueExpression, Object.class);
         Set<ConstraintViolation<Object>> ivs = Validators.instance().validate(ve, facesContext.getELContext(), value);
         if (ivs.size() > 0)
         {
            validationFailed = true;
            String message = ivs.iterator().next().getMessage();
            facesContext.addMessage(null, FacesMessages.createFacesMessage(FacesMessage.SEVERITY_ERROR, message));
            return false;
         }
         else
         {
            return true;
         }
      }

      protected void onException(Exception e)
      {
         throw new AssertionError(e);
      }

      /**
       * Call a method binding
       */
      protected Object invokeMethod(String methodExpression)
      {
         return application.getExpressionFactory().createMethodExpression(facesContext.getELContext(), methodExpression, Object.class, new Class[0]).invoke(facesContext.getELContext(), null);
      }

      /**
       * Simulate an action method
       */
      protected Object invokeAction(String actionMethodExpression)
      {
         action = actionMethodExpression;
         Object result = invokeMethod(actionMethodExpression);
         if (result != null)
         {
            setOutcome(result.toString());
         }
         return result;
      }

      /**
       * @return the conversation id
       * @throws Exception to fail the test
       */
      public String run() throws Exception
      {
         try
         {
            init();
            beforeRequest();
            setStandardJspVariables();
            seamFilter.doFilter(request, response, new FilterChain()
            {
               public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
               {
                  try
                  {
                     if (emulateJsfLifecycle())
                     {
                        saveConversationViewRoot();
                     }
                  }
                  catch (Exception e)
                  {
                     onException(e);
                     throw new ServletException(e);
                  }
               }
            });
            seamFilter.destroy();
            afterRequest();
            facesContext.release();
            return conversationId;
         }
         finally
         {
            if (Contexts.isEventContextActive())
            {
               FacesLifecycle.endRequest(externalContext);
            }
         }

      }

      private void saveConversationViewRoot()
      {
         Map renderedViewRootAttributes = facesContext.getViewRoot().getViewMap();
         if (renderedViewRootAttributes != null && conversationId != null)
         {
            Map conversationState = new HashMap();
            conversationState.putAll(renderedViewRootAttributes);
            conversationViewRootAttributes.put(conversationId, conversationState);
         }
      }

      protected void init()
      {
         request = createRequest();
         response = createResponse();
         externalContext = new MockExternalContext(servletContext, request, response);
         facesContext = new MockFacesContext(externalContext, application);
         facesContext.setCurrent();
      }

      /**
       * Override if you wish to customize the HttpServletRequest used in this request.
       * You may find {@link HttpServletRequestWrapper} useful.
       */
      protected HttpServletRequest createRequest()
      {
         Cookie[] cookieArray = getCookies().toArray(new Cookie[] {});
         return new MockHttpServletRequest(session, getPrincipalName(), getPrincipalRoles(), cookieArray, isGetRequest() ? "GET" : "POST");
      }
      
      /**
       * Override if you wish to customize the HttpServletResponse used in this request.
       * You may find {@link HttpServletResponseWrapper} useful. 
       */
      protected HttpServletResponse createResponse()
      {
         return new MockHttpServletResponse();
      }

      private void setStandardJspVariables()
      {
         // TODO: looks like we should also set request, session, application,
         // page...
         Map<String, String> params = new HashMap<String, String>();
         for (Map.Entry<String, String[]> e : ((Map<String, String[]>) request.getParameterMap()).entrySet())
         {
            if (e.getValue().length == 1)
            {
               params.put(e.getKey(), e.getValue()[0]);
            }
         }
         request.setAttribute("param", params);
      }

      /**
       * @return true if a response was rendered
       */
      private boolean emulateJsfLifecycle() throws Exception
      {
         restoreViewPhase();
         if (!isGetRequest() && !skipToRender())
         {
            afterRestoreViewPhase();

            applyRequestValuesPhase();
            if (!skipToRender())
            {
               processValidationsPhase();
               if (!skipToRender())
               {
                  updateModelValuesPhase();
                  if (!skipToRender())
                  {
                     invokeApplicationPhase();
                  }
               }
            }
         }

         if (skipRender())
         {
            // we really should look at redirect parameters here!
            return false;
         }
         else
         {
            renderResponsePhase();
            return true;
         }
      }

      private void renderResponsePhase() throws Exception
      {
         phases.beforePhase(new PhaseEvent(facesContext, PhaseId.RENDER_RESPONSE, MockLifecycle.INSTANCE));

         try
         {
            updateConversationId();

            renderResponseBegun = true;

            renderResponse();

            renderResponseComplete = true;

            facesContext.getApplication().getStateManager().saveView(facesContext);

            updateConversationId();
         }
         finally
         {
            phases.afterPhase(new PhaseEvent(facesContext, PhaseId.RENDER_RESPONSE, MockLifecycle.INSTANCE));
         }
      }

      private void invokeApplicationPhase() throws Exception
      {
         phases.beforePhase(new PhaseEvent(facesContext, PhaseId.INVOKE_APPLICATION, MockLifecycle.INSTANCE));
         try
         {
            updateConversationId();

            invokeApplicationBegun = true;

            invokeApplication();

            invokeApplicationComplete = true;

            String outcome = getInvokeApplicationOutcome();
            facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, action, outcome);

            viewId = getRenderedViewId();

            updateConversationId();
         }
         finally
         {
            phases.afterPhase(new PhaseEvent(facesContext, PhaseId.INVOKE_APPLICATION, MockLifecycle.INSTANCE));
         }
      }

      private void updateModelValuesPhase() throws Exception
      {
         phases.beforePhase(new PhaseEvent(facesContext, PhaseId.UPDATE_MODEL_VALUES, MockLifecycle.INSTANCE));
         try
         {
            updateConversationId();

            updateModelValues();

            updateConversationId();
         }
         finally
         {
            phases.afterPhase(new PhaseEvent(facesContext, PhaseId.UPDATE_MODEL_VALUES, MockLifecycle.INSTANCE));
         }
      }

      private void processValidationsPhase() throws Exception
      {
         phases.beforePhase(new PhaseEvent(facesContext, PhaseId.PROCESS_VALIDATIONS, MockLifecycle.INSTANCE));
         try
         {
            updateConversationId();

            processValidations();

            updateConversationId();

            if (isValidationFailure())
            {
               facesContext.renderResponse();
            }
         }
         finally
         {
            phases.afterPhase(new PhaseEvent(facesContext, PhaseId.PROCESS_VALIDATIONS, MockLifecycle.INSTANCE));
         }
      }

      private void applyRequestValuesPhase() throws Exception
      {
         phases.beforePhase(new PhaseEvent(facesContext, PhaseId.APPLY_REQUEST_VALUES, MockLifecycle.INSTANCE));
         try
         {
            updateConversationId();

            applyRequestValues();

            updateConversationId();
         }
         finally
         {
            phases.afterPhase(new PhaseEvent(facesContext, PhaseId.APPLY_REQUEST_VALUES, MockLifecycle.INSTANCE));
         }
      }

      private void restoreViewPhase()
      {
         if (conversationId != null)
         {
            setParameter(getConversationIdParameter(), conversationId);
         }
         phases.beforePhase(new PhaseEvent(facesContext, PhaseId.RESTORE_VIEW, MockLifecycle.INSTANCE));
         try
         {
            UIViewRoot viewRoot = facesContext.getApplication().getViewHandler().createView(facesContext, getViewId());
            facesContext.setViewRoot(viewRoot);
            Map restoredViewRootAttributes = facesContext.getViewRoot().getViewMap();
            if (conversationId != null)
            {
               if (isGetRequest())
               {
                  setParameter(Manager.instance().getConversationIdParameter(), conversationId);
                  // TODO: what about conversationIsLongRunning????
               }
               else
               {
                  if (conversationViewRootAttributes.containsKey(conversationId))
                  {
                     // should really only do this if the view id matches (not
                     // really possible to implement)
                     Map state = conversationViewRootAttributes.get(conversationId);
                     restoredViewRootAttributes.putAll(state);
                  }
               }
            }
            if (isGetRequest())
            {
               facesContext.renderResponse();
            }
            else
            {
               restoredViewRootAttributes.putAll(pageParameters);
            }
         }
         finally
         {
            phases.afterPhase(new PhaseEvent(facesContext, PhaseId.RESTORE_VIEW, MockLifecycle.INSTANCE));
         }
      }

      private void updateConversationId()
      {
         Manager manager = Manager.instance();
         conversationId = manager.isLongRunningConversation() ? manager.getCurrentConversationId() : manager.getParentConversationId();
      }

      private boolean skipRender()
      {
         return FacesContext.getCurrentInstance().getResponseComplete();
      }

      private boolean skipToRender()
      {
         return FacesContext.getCurrentInstance().getRenderResponse() || FacesContext.getCurrentInstance().getResponseComplete();
      }

      protected boolean isInvokeApplicationBegun()
      {
         return invokeApplicationBegun;
      }

      protected boolean isInvokeApplicationComplete()
      {
         return invokeApplicationComplete;
      }

      protected boolean isRenderResponseBegun()
      {
         return renderResponseBegun;
      }

      protected boolean isRenderResponseComplete()
      {
         return renderResponseComplete;
      }

      protected MimeMessage getRenderedMailMessage(String viewId)
      {
         installMockTransport();
         MockTransport.clearMailMessage();
         Renderer.instance().render(viewId);
         return MockTransport.getMailMessage();
      }

   }

   public class NonFacesRequest extends Request
   {
      public NonFacesRequest()
      {
      }

      /**
       * @param viewId the view id to be rendered
       */
      public NonFacesRequest(String viewId)
      {
         setViewId(viewId);
      }

      /**
       * @param viewId the view id to be rendered
       * @param conversationId the conversation id
       */
      public NonFacesRequest(String viewId, String conversationId)
      {
         super(conversationId);
         setViewId(viewId);
      }

      @Override
      protected final boolean isGetRequest()
      {
         return true;
      }

      @Override
      protected final void applyRequestValues() throws Exception
      {
         throw new UnsupportedOperationException();
      }

      @Override
      protected final void processValidations() throws Exception
      {
         throw new UnsupportedOperationException();
      }

      @Override
      protected final void updateModelValues() throws Exception
      {
         throw new UnsupportedOperationException();
      }

      @Override
      protected final void invokeApplication() throws Exception
      {
         throw new UnsupportedOperationException();
      }

   }

   public class FacesRequest extends Request
   {

      public FacesRequest()
      {
      }

      /**
       * @param viewId the view id of the form that was submitted
       */
      public FacesRequest(String viewId)
      {
         setViewId(viewId);
      }

      /**
       * @param viewId the view id of the form that was submitted
       * @param conversationId the conversation id
       */
      public FacesRequest(String viewId, String conversationId)
      {
         super(conversationId);
         setViewId(viewId);
      }

      @Override
      protected final boolean isGetRequest()
      {
         return false;
      }

   }

   public void begin()
   {
      session = new MockHttpSession(servletContext);
      ServletLifecycle.beginSession(session);
   }

   public void end()
   {
      ServletLifecycle.endSession(session);
      session = null;
   }
   
   /**
    * Boot Seam. Can be used at class, test group or suite level (e.g.
    * @BeforeClass, @BeforeTest, @BeforeSuite)
    * Use in conjunction with {@link #stopSeam()}.
    * @throws Exception
    */
   protected void startSeam() throws Exception
   {
      if (realServletContext == null && MockSeamListener.getServletContext() != null) {
         realServletContext = MockSeamListener.getServletContext();
      }

      // If the Seam Filter is already initialized, we can grab the real servlet context
      if (realServletContext == null && ServletLifecycle.getServletContext() != null) {
         realServletContext = ServletLifecycle.getServletContext();
      }

      ServletContext realContext = realServletContext;
      this.servletContext = createServletContext(realContext);
      ServletLifecycle.beginApplication(servletContext);
      FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY, MockApplicationFactory.class.getName());
      new Initialization(servletContext).create().init();
      ((Init) servletContext.getAttribute(Seam.getComponentName(Init.class))).setDebug(false);
   }
   
   protected ServletContext createServletContext(ServletContext realContext)
   {
     if (realContext != null)
     {
        ServletContextWrapper wrappedServletContext = new ServletContextWrapper(realContext);
        // TODO: 
        //initServletContext(wrappedServletContext.getInitParameters());
        return wrappedServletContext;
     }
     else
     {
        MockServletContext mockServletContext = new MockServletContext();
        initServletContext(mockServletContext.getInitParameters());
        return mockServletContext;
     }
   }
   
   /**
    * Shutdown Seam. Can be used at class, test group or suite level (e.g
    * @AfterClass, @AfterTest, @AfterSuite)
    * Use in conjunction with {@link #startSeam()}.
    * @throws Exception
    */
   protected void stopSeam() throws Exception
   {
      ServletLifecycle.endApplication();
   }
   
   /**
    * Setup this test class instance
    * Must be run for each test class instance (e.g. @BeforeClass)
    * @throws Exception
    */
   protected void setupClass() throws Exception
   {
      servletContext = ServletLifecycle.getServletContext();
      
      // FactoryFinder cannot be reliably used, as something could have called getFactory sooner
      applicationFactory = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
      //applicationFactory = new MockApplicationFactory();
      
      application = applicationFactory.getApplication();
      conversationViewRootAttributes = new HashMap<String, Map>();
      seamFilter = createSeamFilter();
      FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY, MockFacesContextFactory.class.getName());
      
      for (ELResolver elResolver : getELResolvers())
      {
         application.addELResolver(elResolver);
      }
   }
   
   /**
    * Cleanup this test class instance
    * Must be run for each test class instance (e.g. @AfterClass)
    */
   protected void cleanupClass() throws Exception
   {
      seamFilter.destroy();
      conversationViewRootAttributes = null;
      // applicationFactory.setApplication(null);
   }

   protected Filter createSeamFilter() throws ServletException
   {
      SeamFilter seamFilter = new SeamFilter();
      seamFilter.init(new MockFilterConfig(servletContext));
      return seamFilter;
   }

   /**
    * Override to set up any servlet context attributes.
    */
   public void initServletContext(Map initParams)
   {
   }

   protected InitialContext getInitialContext() throws NamingException
   {
      return Naming.getInitialContext();
   }

   protected UserTransaction getUserTransaction() throws NamingException
   {
      return Transaction.instance();
   }

   /**
    * Get the value of an object field, by reflection.
    */
   protected Object getField(Object object, String fieldName)
   {
      Field field = Reflections.getField(object.getClass(), fieldName);
      if (!field.isAccessible())
         field.setAccessible(true);
      return Reflections.getAndWrap(field, object);
   }

   /**
    * Set the value of an object field, by reflection.
    */
   protected void setField(Object object, String fieldName, Object value)
   {
      Field field = Reflections.getField(object.getClass(), fieldName);
      if (!field.isAccessible())
         field.setAccessible(true);
      Reflections.setAndWrap(field, object, value);
   }

   private static boolean started;

   protected ELResolver[] getELResolvers()
   {
      return new ELResolver[0];
   }

   protected void installMockTransport()
   {
      Contexts.getApplicationContext().set(Seam.getComponentName(MailSession.class), new MailSession("mock").create());

   }

}
