package org.jboss.test.faces.staging; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.EventListener; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.naming.NamingException; import javax.naming.spi.NamingManager; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import javax.servlet.jsp.JspFactory; import org.jboss.test.faces.ApplicationServer; import org.jboss.test.faces.FilterHolder; import org.jboss.test.faces.ServletHolder; import org.jboss.test.faces.TestException; /** * This class implements limited Http servlet container 2.5 functionality. It is * designed for a test purposes only ,so that has a limitations: * <ul> * <li>supports local calls only.</li> * <li>java code only configuration ( no xml files processed ).</li> * <li>just one web application, 'deployed' in the root context.</li> * <li>only one client session</li> * <li>communicates by the local java calls only, no network connection</li> * <li>no JSP compilator support ( but it is possible to register pre-compiled * pages as servlets)</li> * <li>...</li> * </ul> * It is main part of the test framework. * */ public class StagingServer extends ApplicationServer { private static final Class<ServletRequestListener> REQUEST_LISTENER_CLASS = ServletRequestListener.class; private static final Class<ServletRequestAttributeListener> REQUEST_ATTRIBUTE_LISTENER_CLASS = ServletRequestAttributeListener.class; private static final Class<ServletContextListener> CONTEXT_LISTENER_CLASS = ServletContextListener.class; private static final Class<HttpSessionListener> SESSION_LISTENER_CLASS = HttpSessionListener.class; private static final Class<HttpSessionAttributeListener> SESSION_ATTRIBUTE_LISTENER_CLASS = HttpSessionAttributeListener.class; private static final Logger log = ServerLogger.SERVER.getLogger(); private final List<RequestChain> servlets = new ArrayList<RequestChain>(); private RequestChain defaultServlet = new ServletContainer(null, new StaticServlet()); private final List<EventListener> contextListeners = new ArrayList<EventListener>(); private final Map<String, String> initParameters = new HashMap<String, String>(); private final ServerResourceDirectory serverRoot = new ServerResourceDirectoryImpl(); private final Map<String, String> mimeTypes = new HashMap<String, String>(); private InvocationListener invocationListener; private final StagingServletContext context = new LocalContext(); private ServletContext contextProxy; private HttpSession currentSession = null; private ThreadLocal<HttpSession> sessions = new ThreadLocal<HttpSession>(); private List<ServerHttpSession> sessionInstances = new ArrayList<ServerHttpSession>(); private boolean sessionPerThread = false; private int port = 0 /* this server doesn't operate with network ports, so any will suit */; private boolean initialised = false; /** * This inner class links ServletContext calls to the server instance. * * @author asmirnov * */ private class LocalContext extends StagingServletContext { /* * (non-Javadoc) * * @see javax.servlet.ServletContext#getMimeType(java.lang.String) */ public String getMimeType(String file) { int indexOfDot = file.lastIndexOf('.'); // get extension. if (indexOfDot >= 0) { file = file.substring(indexOfDot); } return mimeTypes.get(file); } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingServletContext#valueBound(javax * .servlet.ServletContextAttributeEvent) */ @Override protected void valueBound(ServletContextAttributeEvent event) { // inform listeners. for (EventListener listener : contextListeners) { if (listener instanceof ServletContextAttributeListener) { ServletContextAttributeListener contextListener = (ServletContextAttributeListener) listener; contextListener.attributeAdded(event); } } } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingServletContext#valueReplaced(javax * .servlet.ServletContextAttributeEvent) */ @Override protected void valueReplaced(ServletContextAttributeEvent event) { // inform listeners. for (EventListener listener : contextListeners) { if (listener instanceof ServletContextAttributeListener) { ServletContextAttributeListener contextListener = (ServletContextAttributeListener) listener; contextListener.attributeReplaced(event); } } } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingServletContext#valueUnbound(javax * .servlet.ServletContextAttributeEvent) */ @Override protected void valueUnbound(ServletContextAttributeEvent event) { // inform listeners. for (EventListener listener : contextListeners) { if (listener instanceof ServletContextAttributeListener) { ServletContextAttributeListener contextListener = (ServletContextAttributeListener) listener; contextListener.attributeRemoved(event); } } } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingServletContext#getServerResource * (java.lang.String) */ @Override protected ServerResource getServerResource(String path) { return serverRoot.getResource(new ServerResourcePath(path)); } } /** * This inner class links session object calls to the server instance. * * @author asmirnov * */ private class ServerHttpSession extends StagingHttpSession { /* * (non-Javadoc) * * @see javax.servlet.http.HttpSession#getServletContext() */ public ServletContext getServletContext() { return context; } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingHttpSession#valueBound(javax.servlet * .http.HttpSessionBindingEvent) */ @Override protected void valueBound( final HttpSessionBindingEvent sessionBindingEvent) { // inform session listeners. fireEvent(SESSION_ATTRIBUTE_LISTENER_CLASS, new EventInvoker<HttpSessionAttributeListener>() { public void invoke(HttpSessionAttributeListener listener) { listener.attributeAdded(sessionBindingEvent); } }); } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingHttpSession#valueUnbound(javax. * servlet.http.HttpSessionBindingEvent) */ @Override protected void valueUnbound( final HttpSessionBindingEvent sessionBindingEvent) { // inform session listeners. fireEvent(SESSION_ATTRIBUTE_LISTENER_CLASS, new EventInvoker<HttpSessionAttributeListener>() { public void invoke(HttpSessionAttributeListener listener) { listener.attributeRemoved(sessionBindingEvent); } }); } /* * (non-Javadoc) * * @see * org.jboss.test.faces.staging.StagingHttpSession#valueReplaced(javax * .servlet.http.HttpSessionBindingEvent) */ @Override protected void valueReplaced( final HttpSessionBindingEvent sessionBindingEvent) { // inform session listeners. fireEvent(SESSION_ATTRIBUTE_LISTENER_CLASS, new EventInvoker<HttpSessionAttributeListener>() { public void invoke(HttpSessionAttributeListener listener) { listener.attributeReplaced(sessionBindingEvent); } }); } @Override public void invalidate() { super.invalidate(); setCurrentSession(null); } } /** * */ @SuppressWarnings("unchecked") private <T extends EventListener> int fireEvent(Class<T> listenerClass, EventInvoker<T> invoker) { int errorsCount=0; for (EventListener listener : contextListeners) { if (listenerClass.isInstance(listener)) { try { invoker.invoke((T) listener); } catch (Throwable e) { // Application exceptions should not hung server. log.log(Level.SEVERE, "Exception in listener", e); errorsCount++; } } } return errorsCount; } @Override protected void addDirectory(String directoryPath) { serverRoot.addDirectory(new ServerResourcePath(directoryPath)); } /** * Append executable server object ( {@link Filter} or {@link Servlet} to * the server. * * @see ApplicationServer#addFilter(FilterHolder) * @see ApplicationServer#addServlet(ServletHolder) * * @param servlet */ public void addServlet(RequestChain servlet) { servlets.add(servlet); } public void replaceServlet(RequestChain oldHandler, RequestChain newHandler) { servlets.remove(oldHandler); servlets.add(newHandler); } /** * Add servlet to the server. * * @see ApplicationServer#addFilter(FilterHolder) * @see ApplicationServer#addServlet(ServletHolder) * * @param mapping * servlet mapping * @param servlet * {@link Servlet} instance. */ public void addServlet(String mapping, Servlet servlet) { servlets.add(new ServletContainer(mapping, servlet)); } /** * Get appropriate object ( Filter or Servlet ) for a given path. * * @param path * request path relative to web application context. * @return Appropriate Filter or Servlet executable object to serve given * request. If no servlet was registered for the given path, try to * send requested object directly. */ public RequestChain getServlet(String path) { RequestChain result = null; for (RequestChain servlet : servlets) { if (servlet.isApplicable(path)) { result = servlet; break; } } if (null == result) { // Is requested object exist in the virtual content ? try { URL resource = context.getResource(path); if (null != resource) { // Serve it directly. result = defaultServlet; } } catch (MalformedURLException e) { log.warning("Mailformed request URL " + e.getMessage()); } } return result; } @Override public void addInitParameter(String name, String value) { initParameters.put(name, value); } @Override public void addMimeType(String extension, String mimeType) { mimeTypes.put(extension, mimeType); } @Override public void addContent(String path, String content) { ServerResourcePath resourcePath = new ServerResourcePath(path); serverRoot.addResource(resourcePath, new StringContentServerResource(content)); } @Override public void addResource(String path, String resource) { ServerResourcePath resourcePath = new ServerResourcePath(path); serverRoot.addResource(resourcePath, new ClasspathServerResource( resource)); } @Override public void addResource(String path, URL resource) { serverRoot.addResource(new ServerResourcePath(path), new UrlServerResource(resource)); } @Override public void addWebListener(EventListener listener) { contextListeners.add(listener); } /** * Getter method for 'interceptor' events listener. * * @return the invocationListener */ public InvocationListener getInvocationListener() { return invocationListener; } /** * Set listener which gets events on all calls to any methods of the * {@link ServletContext}, {@link HttpSession}, {@link HttpServletRequest}, * {@link HttpServletResponse} instances in the virtual server. this * interceptor can be used to check internal calls in the tests . * * @param invocationListener * the invocationListener to set */ public void setInvocationListener(InvocationListener invocationListener) { this.invocationListener = invocationListener; } /** * Create instance of the {@link InvocationHandler} for the proxy objects. * This handler fire events to the registered {@link InvocationListener} ( * if present ) after target object method call. * * @return the invocationHandler */ InvocationHandler getInvocationHandler(final Object target) { return new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { InvocationListener listener = getInvocationListener(); try { Object result = method.invoke(target, args); if (null != listener) { listener.afterInvoke(new InvocationEvent(target, method, args, result)); } return result; } catch (Throwable e) { if (null != listener) { listener.processException(new InvocationErrorEvent( target, method, args, e)); } throw e; } } }; } @Override public boolean isSessionPerThread() { return sessionPerThread; } @Override public void setSessionPerThread(boolean sessionPerThread) { this.sessionPerThread = sessionPerThread; } HttpSession getCurrentSession() { if (isSessionPerThread()) { return sessions.get(); } else { return currentSession; } } void setCurrentSession(HttpSession session) { if (isSessionPerThread()) { sessions.set(session); } else { this.currentSession=session; } } @Override public HttpSession getSession() { return getSession(true); } @Override public synchronized HttpSession getSession(boolean create) { if (!initialised) { throw new TestException("Staging server have not been initialised"); } HttpSession httpSession = this.getCurrentSession(); if (null == httpSession && create) { ServerHttpSession sessionImpl = new ServerHttpSession(); // Create proxy objects. ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (null == loader) { loader = this.getClass().getClassLoader(); } httpSession = (HttpSession) Proxy.newProxyInstance(loader, new Class[] { HttpSession.class }, getInvocationHandler(sessionImpl)); setCurrentSession(httpSession); // inform session listeners. final HttpSessionEvent event = new HttpSessionEvent(httpSession); fireEvent(SESSION_LISTENER_CLASS, new EventInvoker<HttpSessionListener>() { public void invoke(HttpSessionListener listener) { listener.sessionCreated(event); } }); sessionInstances.add(sessionImpl); } return httpSession; } private void readDefaultMimeTypes() { InputStream is = null; try { is = Thread.currentThread().getContextClassLoader().getResourceAsStream("org/jboss/test/faces/staging/mime.properties"); Properties props = new Properties(); props.load(is); for (Object key : props.keySet()) { mimeTypes.put("." + key, props.getProperty((String) key)); } } catch (IOException e) { log.log(Level.SEVERE, e.getMessage(), e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { log.log(Level.WARNING, e.getMessage(), e); } } } } @Override public void init() { log.info("Init staging server"); readDefaultMimeTypes(); // Create Jsp factory JspFactory.setDefaultFactory(new StaggingJspFactory(this.context)); // Create init parameters context.addInitParameters(initParameters); // Inform listeners // Create proxy objects. ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (null == loader) { loader = this.getClass().getClassLoader(); } this.contextProxy = (ServletContext) Proxy.newProxyInstance(loader, new Class[] { ServletContext.class }, getInvocationHandler(context)); // Create default servlet final ServletContextEvent event = new ServletContextEvent(context); if(fireEvent(CONTEXT_LISTENER_CLASS, new EventInvoker<ServletContextListener>() { public void invoke(ServletContextListener listener) { listener.contextInitialized(event); } })>0){ throw new TestException("Server not started due to listener error "); } // Init servlets try { for (RequestChain servlet : servlets) { // init servlet servlet.init(context); } defaultServlet.init(context); } catch (ServletException e) { throw new TestException("Servlet initialisation error ", e); } try { NamingManager.setInitialContextFactoryBuilder(new StagingInitialContextFactoryBuilder()); } catch (NamingException e) { log.warning("Error set initial context factory builder."); } catch (IllegalStateException e) { log.warning("Initial context factory builder already set."); } this.initialised = true; } @Override public void destroy() { if (!initialised) { throw new TestException("Staging server have not been initialised"); } this.initialised = false; // Destroy session // TODO - destroy all threads. for (Iterator<ServerHttpSession> sessionIterator = sessionInstances.iterator(); sessionIterator.hasNext();) { ServerHttpSession session = sessionIterator.next(); // inform session listeners. final HttpSessionEvent event = new HttpSessionEvent(session); fireEvent(SESSION_LISTENER_CLASS, new EventInvoker<HttpSessionListener>() { public void invoke(HttpSessionListener listener) { listener.sessionDestroyed(event); } }); session.invalidate(); sessionIterator.remove(); } setCurrentSession(null); // Inform listeners final ServletContextEvent event = new ServletContextEvent(context); fireEvent(CONTEXT_LISTENER_CLASS, new EventInvoker<ServletContextListener>() { public void invoke(ServletContextListener listener) { listener.contextDestroyed(event); } }); // Destroy servlets for (RequestChain servlet : servlets) { servlet.destroy(); } defaultServlet.destroy(); // Clear Jsp factory JspFactory.setDefaultFactory(null); this.contextProxy = null; log.info("Staging server have been destroyed"); } /** * Get virtual connection to the given URL. Even thought for an http request * to the external servers, only local connection to the virtual server will * be created. * * @param url * request url. * @return local connection to the appropriate servlet in the virtual * server. * @throws {@link TestException} if no servlet found to process given URL. */ @Override public StagingConnection getConnection(URL url) { if (!initialised) { throw new TestException("Staging server have not been initialised"); } return new StagingConnection(this, url); } @Override public ServletContext getContext() { if (!initialised) { throw new TestException("Staging server have not been initialised"); } return contextProxy; } /** * Inform {@link ServletRequestListener} instances. For internal use only. * * @param request * started request. */ void requestStarted(ServletRequest request) { final ServletRequestEvent event = new ServletRequestEvent(context, request); fireEvent(REQUEST_LISTENER_CLASS, new EventInvoker<ServletRequestListener>() { public void invoke(ServletRequestListener listener) { listener.requestInitialized(event); } }); } /** * Inform {@link ServletRequestListener} instances. For internal use only. * * @param request * finished request. */ void requestFinished(ServletRequest request) { final ServletRequestEvent event = new ServletRequestEvent(context, request); fireEvent(REQUEST_LISTENER_CLASS, new EventInvoker<ServletRequestListener>() { public void invoke(ServletRequestListener listener) { listener.requestDestroyed(event); } }); } void requestAttributeAdded(ServletRequest request, String name, Object o) { final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent( context, request, name, o); fireEvent(REQUEST_ATTRIBUTE_LISTENER_CLASS, new EventInvoker<ServletRequestAttributeListener>() { public void invoke(ServletRequestAttributeListener listener) { listener.attributeAdded(event); } }); } void requestAttributeRemoved(ServletRequest request, String name, Object removed) { final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent( context, request, name, removed); fireEvent(REQUEST_ATTRIBUTE_LISTENER_CLASS, new EventInvoker<ServletRequestAttributeListener>() { public void invoke(ServletRequestAttributeListener listener) { listener.attributeRemoved(event); } }); } void requestAttributeReplaced(ServletRequest request, String name, Object value) { final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent( context, request, name, value); fireEvent(REQUEST_ATTRIBUTE_LISTENER_CLASS, new EventInvoker<ServletRequestAttributeListener>() { public void invoke(ServletRequestAttributeListener listener) { listener.attributeReplaced(event); } }); } @Override public void addFilter(FilterHolder filterHolder) { Map<String, String> initParameters = filterHolder.getInitParameters(); String mapping = filterHolder.getMapping(); String name = filterHolder.getName(); Filter filter = filterHolder.getFilter(); RequestChain oldHandler = getServlet(mapping); FilterContainer newHandler = new FilterContainer(filter, oldHandler); newHandler.setName(name); if (initParameters != null) { for (Entry<String, String> initEntry : initParameters.entrySet()) { newHandler.addInitParameter(initEntry.getKey(), initEntry.getValue()); } } replaceServlet(oldHandler, newHandler); } @Override public void addServlet(ServletHolder servletHolder) { Map<String, String> initParameters = servletHolder.getInitParameters(); String mapping = servletHolder.getMapping(); String name = servletHolder.getName(); Servlet servlet = servletHolder.getServlet(); ServletContainer servletContainer = new ServletContainer(mapping, servlet); servletContainer.setName(name); if (initParameters != null) { for (Entry<String, String> initEntry : initParameters.entrySet()) { servletContainer.addInitParameter(initEntry.getKey(), initEntry.getValue()); } } addServlet(servletContainer); } @Override public int getPort() { return port; } }