/** Copyright (c) Oracle Corporation 1998. All Rights Reserved. */ package oracle.jbo.html.jsp; import java.io.PrintWriter; import java.io.OutputStream; import java.io.FileInputStream; import java.io.InputStream; import java.util.Vector; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.Properties; import java.util.Hashtable; import oracle.jbo.ApplicationModule; import oracle.jbo.domain.TypeFactory; import oracle.jdeveloper.html.WebBeanImpl; import oracle.jdeveloper.cm.ConnectionManager; import oracle.jdeveloper.cm.ConnectionDescriptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import javax.servlet.jsp.PageContext; import oracle.jbo.common.ampool.PoolMgr; import oracle.jbo.common.ampool.ApplicationPool; import oracle.jbo.common.ampool.ApplicationPoolException; import oracle.jbo.common.ampool.AMPoolMessageBundle; import oracle.jbo.common.Diagnostic; import oracle.jbo.common.PropertyMetadata; import oracle.jbo.common.PropertyConstants; import oracle.jbo.http.HttpContainer; import oracle.jbo.http.ApplicationBindingListener; import oracle.jbo.html.jsp.datatags.ReleasePageResourcesTag; /** ** This class provides the main interface for DataWebBeans to use the Application Module Pool. ** ** View implementation of JSPApplicationRegistry ** ** @author Juan Oropeza ** @version PUBLIC ** **/ public class JSPApplicationRegistry extends WebBeanImpl { static JSPApplicationRegistry mInstance = new JSPApplicationRegistry(); static PoolMgr mPoolManager = PoolMgr.getInstance(); private static final String TIMEOUT_HANDLER_SUFFIX = "_TimeoutHandler"; public static final String RESERVED = PropertyConstants.AM_RELEASE_RESERVED; public static final String STATEFUL = PropertyConstants.AM_RELEASE_STATEFUL; public static final String STATELESS = PropertyConstants.AM_RELEASE_STATELESS; private static final String SESSION_INITIALIZED = "SessionInitialized"; /** ** Constructor, this should not be called directly **/ public JSPApplicationRegistry() { } /** ** Returns the singleton instance of the registry class. **/ static public JSPApplicationRegistry getInstance() { return mInstance; } /** ** Returns the user data associated with the named pool. The user data is a convenient palce for storing ** and retrieving application-specific information shared by all application instances that are part of the ** named pool. **/ static synchronized public Hashtable getAppSettings(String appName) { ApplicationPool pool = mPoolManager.getPool(appName); return (Hashtable)pool.getUserData(); } /** ** This is a convenience method for returning an Application Module back to ** the pool. This method will find the ApplicationInstance associated with ** the Application Module and check it in to the pool. ** ** @deprecated As of BC4J 3.2, replaced by {@link #releaseAppModuleInstance(String, HttpSession, HttpServletResponse, String)} **/ static synchronized public void returnAppModuleInstance( String appName, ApplicationModule appModule) { getInstance().releaseAppModuleInstance( appName , getInstance().session , getInstance().response , getInstance().getReleaseMode(appName)); } /** * Release the specified application module instance. This method is * intended for use by servlet clients in order to release an application * module resource to the application module pool.
** The specified release mode will determine how the application module is * returned to the pool. The following release modes are currently * supported:
** RESERVED: Do not allow the application module instance to be * shared with other client requests. The reserved lock will be released * when the application module is checked in by a latter request in the * application life cycle or when the http session times out.
** STATEFUL: Only available if a HttpServletResponse has been * specified. The implementation will check the application module * instance into the pool in a stateful manner which will allow the pool * to maintain the application module state between session requests. * This may involve passivating the session's application module state so * that the application may be reused by other session requests. Using * this option will cause a servlet cookie to be generated and written * to the HttpServletResponse.
** STATELESS: Check the application module instance into the pool * without retaining application module state.
* * @see oracle.jbo.common.ampool.ApplicationPool ApplicationPool */ public void releaseAppModuleInstance( ApplicationModule appModule , ApplicationPool pool , HttpSession session , HttpServletResponse response , String releaseMode) { // Only proceed if the application is not available. Otherwise, // the application module has been successfully checked in already. // This check is necessary to prevent redundant check-ins to the pool. // The check is implemented here because the JSPApplicationRegistry // may potentially register two application module references in the // HTTP contexts (page and session). if (!pool.isAvailable(appModule)) { // If the application release mode has been specified as reserved // then promote the application module to the session level context. // The application module will be checked in if it is used in a page // again with a release mode other than reserved or if the session // times out. if (JSPApplicationRegistry.RESERVED.equals(releaseMode)) { Diagnostic.println("ApplicationModule release mode is: Reserved"); // Add the application module to the BC4J session context container. addAppModuleToSession(appModule, pool, session); } // If a stateful release mode has been specified then passivate // the application module and return it to the application pool. This // mode is not supported if a response object has not been specified. else if ((response != null) && (JSPApplicationRegistry.STATEFUL.equals(releaseMode))) { Diagnostic.println("ApplicationModule release mode is: Stateful"); String sessionId = pool.checkinWithSessionState(appModule); writeSessionId(response, pool.getPoolName(), sessionId); HttpContainer container = (HttpContainer)session .getValue(HttpContainer.SESSION_CONTEXT_CONTAINER_NAME); // Remove the application module from the JBO session context // container try { if (container != null) { container.removeValueInternal( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName()); container.removeValueInternal( HttpContainer.APPLICATION_PREFIX + pool.getPoolName()); } } catch (IllegalStateException iex) { // If an illegal state exception is thrown then the session // has already been cleaned up. Eat the exception and continue. } } // If the release mode is stateless then simply return the application // module to the pool. else { Diagnostic.println("ApplicationModule release mode is: Stateless"); pool.checkin(appModule); try { HttpContainer container = (HttpContainer)session .getValue(HttpContainer.SESSION_CONTEXT_CONTAINER_NAME); // Remove the application module from the JBO session context // container if (container != null) { container.removeValueInternal( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName()); container.removeValueInternal( HttpContainer.APPLICATION_PREFIX + pool.getPoolName()); } } catch (IllegalStateException iex) { // If an illegal state exception is thrown then the session // has already been cleaned up. Eat the exception and continue. } } } } /** * Release the specified application module instance. This method is * intended for use by JSP clients in order to release an application * module resource to the application module pool. ** The specified release mode will determine how the application module is * returned to the pool. The following release modes are currently * supported:
** RESERVED: Do not allow the application module instance to be * shared with other client requests. The reserved lock will be released * when the application module is checked in by a latter request in the * application life cycle or when the http session times out.
** STATEFUL: Only available if a HttpServletResponse has been * specified. The implementation will check the application module * instance into the pool in a stateful manner which will allow the pool * to maintain the application module state between session requests. * This may involve passivating the session's application module state so * that the application may be reused by other session requests. Using * this option will cause a servlet cookie to be generated and written * to the HttpServletResponse.
** STATELESS: Check the application module instance into the pool * without retaining application module state.
* * @see oracle.jbo.common.ampool.ApplicationPool ApplicationPool */ public void releaseAppModuleInstance( ApplicationModule appModule , ApplicationPool pool , PageContext pageContext , String releaseMode) { HttpSession session = pageContext.getSession(); HttpServletResponse response = (HttpServletResponse)pageContext.getResponse(); releaseAppModuleInstance( appModule , pool , session , response , releaseMode); HttpContainer container = (HttpContainer)pageContext .getAttribute(HttpContainer.PAGE_CONTEXT_CONTAINER_NAME); // Remove the application module from JBO page context container. if (container != null) { container.removeValueInternal( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName()); container.removeValueInternal( HttpContainer.APPLICATION_PREFIX + pool.getPoolName()); } } /** * Release the application module instance that was generated by the * specified pool from the BC4J session context. */ public void releaseAppModuleInstance(String poolName , HttpSession session , HttpServletResponse response , String releaseMode) { HttpContainer container = (HttpContainer)session .getValue(HttpContainer.SESSION_CONTEXT_CONTAINER_NAME); if (container != null) { Properties props = new Properties(); props.put(ApplicationBindingListener.RELEASE_MODE, releaseMode); props.put(ApplicationBindingListener.HTTP_RESPONSE, response); container.removeValue( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + poolName , props); } } /** * Get an application module instance for the specified application pool. * This method is intended for servlet clients that require an application * module. ** The implementation will attempt to locate an application * module in the session context before asking the * application pool for an application module.
** If an application module is not found in the session or page contexts * the implementation will check the http request for a session cookie id. * If a session cookie id exists the implementation will invoke, * {@link oracle.jbo.common.ampool.ApplicationPool#getInstanceWithSessionId getInstanceWithSessionId}, * in order to return an application module with the same state as the * application module that had been checked in by the session.
** If a session cookie id does not exist the implementation will invoke, * {@link oracle.jbo.common.ampool.ApplicationPool#checkout() checkout} in order * to return a fresh application module (no existing state) to the invokee.
** The returned application module is always checked out of the specified * application pool.
* * @see oracle.jdeveloper.html.DataWebBeanImpl#internalInitialize() */ public ApplicationModule getAppModuleInstance( String appName , HttpServletRequest request , HttpSession session) { ApplicationPool pool = mPoolManager.getPool(appName); ApplicationModule appModule = getAppModuleFromContexts( appName , session , null); // If the application module was not located in the http contexts if (appModule == null) { appModule = internalGetAppModuleInstance(appName, pool, request); } addAppModuleToSession(appModule, pool, session); return appModule; } /** * Get an application module instance for the specified application pool. * This method is intended for JSP clients that require an application module. ** The implementation will attempt to locate an application * module in the page and session contexts before asking the * application pool for an application module.
** If an application module is not found in the session or page contexts * the implementation will check the http request for a session cookie id. * If a session cookie id exists the implementation will invoke, * {@link oracle.jbo.common.ampool.ApplicationPool#getInstanceWithSessionId() getInstanceWithSessionId}, * in order to return an application module with the same state as the * application module that had been checked in by the session.
** If a session cookie id does not exist the implementation will invoke, * {@link oracle.jbo.common.ampool.ApplicationPool#checkout() checkout} in order * to return a fresh application module (no existing state) to the invokee.
** The returned application module is always checked out of the specified * application pool.
* * @see oracle.jbo.html.jsp.datatags.ApplicationModuleTag#doEndTag */ public ApplicationModule getAppModuleInstance( String appName , PageContext pageContext) { ApplicationPool pool = mPoolManager.getPool(appName); HttpSession session = pageContext.getSession(); HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); ApplicationModule appModule = getAppModuleFromContexts( appName , session , pageContext); // If the application module was not located in the http contexts if (appModule == null) { appModule = internalGetAppModuleInstance(appName, pool, request); } addAppModuleToPage(appModule, pool, pageContext, session); // It is necessary to place the appmodule in the session context // container so that it may be reused by the data web beans. addAppModuleToSession(appModule, pool, session); return appModule; } private ApplicationModule internalGetAppModuleInstance( String appName , ApplicationPool pool , HttpServletRequest request) { ApplicationModule appModule = null; String sessionId = readSessionId(request, appName); if (sessionId != null) { appModule = pool.checkout(sessionId); } else { try { appModule = pool.checkout(); } catch (Exception ex) { Diagnostic.printStackTrace(ex); ApplicationPoolException aex = new ApplicationPoolException( AMPoolMessageBundle.class , AMPoolMessageBundle.EXC_AMPOOL_CHECKOUT_FAILED , new Object[] {appName}); aex.addToDetails(ex); throw aex; } } return appModule; } /** * Check the relevant http contexts for the named application module * instance. ** If the page context is not null then first check the page context * for a Bc4jPageContext container. If one is found then check for * the specified application module instance. Otherwise check if the * specified application module instance has been placed in the session * context.
* * @param appModuleName the name of the appModule instance that should be * retrieved * @param session the HttpSession context that should be checked for the * named application module instance * @param pageContext if a JSP client the name of the pageContext that * should be checked for the named application module instance. Null * otherwise. */ public ApplicationModule getAppModuleFromContexts( String appName , HttpSession session , PageContext pageContext) { ApplicationModule appModule = null; HttpContainer container = null; if (pageContext != null) { container = (HttpContainer)pageContext .getAttribute(HttpContainer.PAGE_CONTEXT_CONTAINER_NAME); if (container != null) { appModule = (ApplicationModule)container .getValue(HttpContainer.APPLICATION_PREFIX + appName); } } // If the appModule was not located in the page context if (appModule == null) { synchronized(session) { container = (HttpContainer)session .getValue(HttpContainer.SESSION_CONTEXT_CONTAINER_NAME); if (container != null) { appModule = (ApplicationModule)container .getValue(HttpContainer.APPLICATION_PREFIX + appName); } } } return appModule; } /** ** Locates the application pool and check out a new application module. You can use the ** {@link #releaseAppModuleInstance(String, HttpSession, HttpServletResponse, String)} method ** to return the Application Module back to the pool. ** ** @deprecated As of BC4J 3.2, replaced by {@link #getAppModuleInstance(String, HttpServletRequest, HttpSession)} **/ static synchronized public ApplicationModule getApplication(String appName) throws Exception { return getInstance().getAppModuleInstance( appName , getInstance().request , getInstance().session); } /** ** Convenience method for defining a new application pool from a property file. This also transfers some system ** specific variables to the JSP Session object. **/ static synchronized public void registerApplicationFromPropertyFile(HttpSession session, String sPropFileName) { if(!mPoolManager.isPoolCreated(sPropFileName)) { registerApplicationFromPropertyFile(sPropFileName); } if (!PropertyConstants.TRUE.equals((String)session.getValue(SESSION_INITIALIZED))) { Hashtable settings = getAppSettings(sPropFileName); int nTimeOut = 300; if (settings != null) { // see if we have a setting for the session timeout String sTimeOut; if(settings.get("HttpSessionTimeOut") != null) { sTimeOut = (String)settings.get("HttpSessionTimeOut"); if(sTimeOut != null) { nTimeOut = Integer.parseInt(sTimeOut); } } if(settings.get("ImageBase") != null) { session.putValue("ImageBase", settings.get("ImageBase")); } else { settings.put("ImageBase", "/webapp/images"); session.putValue("ImageBase", "/webapp/images"); } if(settings.get("CSSURL") != null) { session.putValue("CSSURL",settings.get("CSSURL")); } else { settings.put("CSSURL", "/webapp/css/oracle.css"); session.putValue("CSSURL", "/webapp/css/oracle.css"); } } // place default renderers into session, these will not be // exposed via config file session.putValue("oracle_ord_im_OrdImageDomain_Renderer", "oracle.ord.html.OrdBuildURL"); session.putValue("oracle_ord_im_OrdAudioDomain_Renderer","oracle.ord.html.OrdBuildURL"); session.putValue("oracle_ord_im_OrdVideoDomain_Renderer","oracle.ord.html.OrdBuildURL"); session.putValue("oracle_ord_im_OrdVirDomain_Renderer", "oracle.ord.html.OrdBuildURL"); session.putValue("oracle_ord_im_OrdImageDomain_EditRenderer", "oracle.ord.html.FileUploadField"); session.putValue("oracle_ord_im_OrdAudioDomain_EditRenderer", "oracle.ord.html.FileUploadField"); session.putValue("oracle_ord_im_OrdVideoDomain_EditRenderer", "oracle.ord.html.FileUploadField"); session.putValue("oracle_ord_im_OrdVirDomain_EditRenderer", "oracle.ord.html.FileUploadField"); session.putValue(SESSION_INITIALIZED, PropertyConstants.TRUE); } } /** ** Creates a new application pool from the contents of a property file. Please look at the ConnectionInfo class ** for documentation of what should be contained in the property file. **/ static synchronized public void registerApplicationFromPropertyFile(String sPropFileName) { try { String sConnectUrl; String sAppModuleClass; if(!mPoolManager.isPoolCreated(sPropFileName)) { // open up the property file String sConfigPath = sPropFileName + ".properties"; // find the property file in the classpath System.out.println("Loading from CLASSPATH " + sConfigPath); InputStream in = ClassLoader.getSystemResourceAsStream(sConfigPath); if(in == null) { throw new RuntimeException("JSP Registry could not locate runtime property file:" + sConfigPath); } // load up the properties file Properties props = new Properties(); props.load(in); in.close(); // check if we are using a configuration (this started in 3.2) if(props.get("ConfigName") != null) { String sConfigName = props.get("ConfigName").toString(); String sConfigPackage = sConfigName.substring(0, sConfigName.lastIndexOf('.')); String sConfigSection = sConfigName.substring(sConfigName.lastIndexOf('.') + 1); // strip out the am class sConfigPackage = sConfigPackage.substring(0, sConfigPackage.lastIndexOf('.')); ApplicationPool pool = mPoolManager.createPool(sPropFileName, sConfigPackage, sConfigSection, props); if(props.get("Password") != null) pool.setPassword(props.get("Password").toString()); if(props.get("UserName") != null) pool.setUserName(props.get("UserName").toString()); } else { // this handles backward compatibility ConnectionInfo connectInfo = new ConnectionInfo(props); ApplicationPool pool = mPoolManager.createPool( sPropFileName , connectInfo.getPoolClass() , connectInfo.getAppModuleClass() , connectInfo.getConnectionUrl() , connectInfo.getConnectionSettings()); pool.setUserName(connectInfo.sUserName); pool.setPassword(connectInfo.sPassword); pool.setUserData(props); } } } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex.toString()); } } /** * Helper method to obtain the specified application's release mode. */ public String getReleaseMode(String appName) { Hashtable appProps = getAppSettings(appName); String releaseMode = ((appProps != null) && (appProps.get(PropertyMetadata.AM_RELEASE_MODE.pName) != null)) ? (String)appProps.get(PropertyMetadata.AM_RELEASE_MODE.pName) : null; // If the stateless runtime application property value is true then set // the release mode to be stateful. Otherwise set the release mode to be // reserved. // Required for backwards compatibility with 3.1 if ((releaseMode == null) && (appProps != null) && (appProps.get(PropertyConstants.IS_STATELESS_RUNTIME) != null)) { releaseMode = ((String)appProps.get(PropertyConstants.IS_STATELESS_RUNTIME)) .equals(PropertyConstants.TRUE) ? STATEFUL : RESERVED; } // Finally, if the release mode is still null set it to be reserved. if (releaseMode == null) { releaseMode = RESERVED; } return releaseMode; } /** * Read the session id from the HttpServletRequest. By default this method * uses Http cookies to store the unique session identifier for a * stateful application module. * * @param applicationName the name of the application that has session state */ protected String readSessionId( HttpServletRequest request , String applicationName) { Cookie[] cookies = request.getCookies(); Cookie cookie = null; if (cookies != null) { for (int i=0; i < cookies.length; i++) { if (cookies[i].getName().equals( HttpContainer.APPLICATION_COOKIE_PREFIX + applicationName)) { cookie = cookies[i]; break; } } } return (cookie != null) ? cookie.getValue() : null; } /** * Write the session id to the HttpServletResponse. By default this method * uses Http cookies to store a unique session identifier for a * stateful application module. * * @param applicationName the name of the application that has session state */ protected void writeSessionId( HttpServletResponse response , String applicationName , String sessionId) { Hashtable appSettings = getAppSettings(applicationName); String maxAgeStr = (String)appSettings.get(PropertyConstants.ENV_MAX_POOL_COOKIE_AGE); int maxAge = (maxAgeStr != null) ? Integer.parseInt(maxAgeStr) : -1; Cookie cookie = new Cookie( HttpContainer.APPLICATION_COOKIE_PREFIX + applicationName , sessionId); if (maxAge >= 0) { cookie.setMaxAge(maxAge); } response.addCookie(cookie); } private void addAppModuleToPage( ApplicationModule appModule , ApplicationPool pool , PageContext pageContext , HttpSession session) { HttpContainer container = (HttpContainer)pageContext .getAttribute(HttpContainer.PAGE_CONTEXT_CONTAINER_NAME); if (container == null) { container = new HttpContainer(pageContext); pageContext.setAttribute( HttpContainer.PAGE_CONTEXT_CONTAINER_NAME , container); // Add the new pageContext to the session context, just in // case the page context is orphaned. This will ensure that // any page resources will get cleaned up when // the session times out. synchronized(session) { // Add the page context container to the session only if it has not // already been added if (session.getValue( HttpContainer.PAGE_CONTEXT_CONTAINER_NAME) == null) { session.putValue( HttpContainer.PAGE_CONTEXT_CONTAINER_NAME , container); } } } // Add the application module reference to the page context only if it // has not been added already if (container.getValue( HttpContainer.APPLICATION_PREFIX + pool.getPoolName()) == null) { container.putValue( HttpContainer.APPLICATION_PREFIX + pool.getPoolName() , appModule , null); } if (container.getValue( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName()) == null) { container.putValue( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName() , new ApplicationBindingListener(appModule, pool) , null ); } } private void addAppModuleToSession( ApplicationModule appModule , ApplicationPool pool , HttpSession session) { // Add the application module to the BC4J session context container. // Check out a lock on the session context to prevent // multiple threads from simultaneously attempting to create // JBO contexts. synchronized(session) { HttpContainer container = getHttpContainer(session); // Add the application module reference to the session only if it // has not been added already if (container.getValue( HttpContainer.APPLICATION_PREFIX + pool.getPoolName()) == null) { container.putValue( HttpContainer.APPLICATION_PREFIX + pool.getPoolName() , appModule , null); } if (container.getValue( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName()) == null) { container.putValue( HttpContainer.APPLICATION_BINDING_LISTENER_PREFIX + pool.getPoolName() , new ApplicationBindingListener(appModule, pool) , null); } } } static synchronized public HttpContainer getHttpContainer(HttpSession session) { HttpContainer container = (HttpContainer)session.getValue(HttpContainer.SESSION_CONTEXT_CONTAINER_NAME); if (container == null) { container = new HttpContainer(session); session.putValue(HttpContainer.SESSION_CONTEXT_CONTAINER_NAME , container); } return container; } }