001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.pageflow.internal;
020:
021: import org.apache.beehive.netui.pageflow.AutoRegisterActionServlet;
022: import org.apache.beehive.netui.pageflow.ServletContainerAdapter;
023: import org.apache.beehive.netui.pageflow.RequestContext;
024: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
025: import org.apache.beehive.netui.pageflow.handler.ReloadableClassHandler;
026: import org.apache.beehive.netui.pageflow.handler.HandlerConfig;
027: import org.apache.beehive.netui.pageflow.handler.Handler;
028: import org.apache.beehive.netui.pageflow.handler.Handlers;
029: import org.apache.beehive.netui.util.internal.BouncyClassLoader;
030: import org.apache.beehive.netui.util.internal.cache.ClassLevelCache;
031: import org.apache.beehive.netui.util.logging.Logger;
032: import org.apache.beehive.netui.util.internal.DiscoveryUtils;
033:
034: import javax.servlet.http.HttpSession;
035: import javax.servlet.ServletContext;
036: import javax.servlet.ServletRequest;
037: import java.util.Enumeration;
038: import java.util.Map;
039: import java.util.ArrayList;
040: import java.util.Iterator;
041:
042: import org.apache.beehive.netui.util.internal.concurrent.InternalConcurrentHashMap;
043: import java.io.File;
044: import java.io.InputStream;
045: import java.net.URL;
046:
047: import org.apache.struts.action.ActionServlet;
048:
049: /**
050: * Handler that can load classes through a classloader that gets dropped/refreshed when time stamps on binary files
051: * change.
052: */
053: public class DefaultReloadableClassHandler extends DefaultHandler
054: implements ReloadableClassHandler {
055: private static final Logger _log = Logger
056: .getInstance(DefaultReloadableClassHandler.class);
057: private static final boolean _enabled = System
058: .getProperty("pageflow.bouncy") != null;
059:
060: private transient BouncyClassLoader _pageFlowClassLoader = null;
061:
062: public DefaultReloadableClassHandler(ServletContext servletContext) {
063: init(null, null, servletContext);
064:
065: }
066:
067: public void init(HandlerConfig handlerConfig,
068: Handler previousHandler, ServletContext servletContext) {
069: super .init(handlerConfig, previousHandler, servletContext);
070:
071: // This feature is in prototype mode, and only enabled through a System property for now.
072: if (_enabled) {
073: _log.info("Reloadable classes are enabled.");
074: createClassLoader(servletContext);
075: }
076: }
077:
078: public void reinit(ServletContext servletContext) {
079: super .reinit(servletContext);
080:
081: if (_enabled && _pageFlowClassLoader == null) {
082: createClassLoader(servletContext);
083: }
084: }
085:
086: private void createClassLoader(ServletContext servletContext) {
087: ServletContainerAdapter servletContainerAdapter = AdapterManager
088: .getServletContainerAdapter(servletContext);
089:
090: if (servletContainerAdapter.isInProductionMode()) {
091: _log
092: .info("In production mode; reloadable classes disabled.");
093: return;
094: }
095:
096: ClassLoader contextClassLoader = Thread.currentThread()
097: .getContextClassLoader();
098: File[] classDirs = null;
099:
100: // TODO: make this configurable in netui-config.xml. You should be able to specify absolute files
101: // and also context-relative paths.
102: {
103: ArrayList classDirsList = new ArrayList();
104:
105: String path = servletContext
106: .getRealPath("/WEB-INF/reloadableClasses");
107: if (path != null) {
108: File file = new File(path);
109: if (file.isDirectory()) {
110: classDirsList.add(file);
111: }
112: }
113:
114: path = servletContext.getRealPath("/WEB-INF/classes");
115: if (path != null) {
116: File file = new File(path);
117: if (file.isDirectory()) {
118: classDirsList.add(file);
119: }
120: }
121:
122: if (!classDirsList.isEmpty()) {
123: classDirs = (File[]) classDirsList
124: .toArray(new File[classDirsList.size()]);
125: }
126: }
127:
128: if (classDirs != null) {
129: _pageFlowClassLoader = new BouncyClassLoader(classDirs,
130: contextClassLoader);
131:
132: StringBuffer message = new StringBuffer(
133: "Reloadable Page Flow classes enabled; using class directories ");
134: for (int i = 0; i < classDirs.length; i++) {
135: if (i != 0) {
136: message.append(", ");
137: }
138: message.append(classDirs[i]);
139: }
140: // TODO: remove the following println when this feature is done.
141: System.out.println("*** " + message);
142: _log.info(message);
143: }
144: }
145:
146: public Object newInstance(String className)
147: throws ClassNotFoundException, InstantiationException,
148: IllegalAccessException {
149: return getRegisteredReloadableClassHandler().loadClass(
150: className).newInstance();
151: }
152:
153: private static Map/*< String, Class >*/_loadedClasses = new InternalConcurrentHashMap/*< String, Class >*/();
154:
155: private static class Null {
156: }
157:
158: private static Class NULL_CLASS = Null.class;
159:
160: public Class loadCachedClass(String className) {
161: Class clazz = (Class) _loadedClasses.get(className);
162:
163: if (clazz != null) {
164: return clazz != NULL_CLASS ? clazz : null;
165: } else {
166: try {
167: clazz = getRegisteredReloadableClassHandler()
168: .loadClass(className);
169: _loadedClasses.put(className, clazz);
170: return clazz;
171: } catch (ClassNotFoundException e) {
172: _loadedClasses.put(className, NULL_CLASS);
173: return null;
174: }
175: }
176: }
177:
178: public Class loadClass(String className)
179: throws ClassNotFoundException {
180: if (_pageFlowClassLoader != null) {
181: synchronized (this ) {
182: return _pageFlowClassLoader.loadClass(className);
183: }
184: }
185:
186: return DiscoveryUtils.getClassLoader().loadClass(className);
187: }
188:
189: public URL getResource(String name) {
190: if (_pageFlowClassLoader != null) {
191: synchronized (this ) {
192: return _pageFlowClassLoader.getResource(name);
193: }
194: }
195:
196: return DiscoveryUtils.getClassLoader().getResource(name);
197: }
198:
199: public InputStream getResourceAsStream(String name) {
200: if (_pageFlowClassLoader != null) {
201: synchronized (this ) {
202: return _pageFlowClassLoader.getResourceAsStream(name);
203: }
204: }
205:
206: return DiscoveryUtils.getClassLoader()
207: .getResourceAsStream(name);
208: }
209:
210: public void reloadClasses(RequestContext context) {
211: if (_pageFlowClassLoader == null) {
212: return;
213: }
214:
215: synchronized (this ) {
216: if (_pageFlowClassLoader.isStale()) {
217: // TODO: remove the following println when this feature is done.
218: System.out
219: .println("*** Classes/resources modified; bouncing classloader.");
220:
221: _log
222: .info("Classes/resources modified; bouncing classloader.");
223:
224: // First go through the session and request and remove any attributes whose classes were loaded by the
225: // stale classloader.
226: ServletRequest request = context.getRequest();
227: removeSessionAttributes(request);
228: removeRequestAttributes(request);
229: removeRequestAttributes(ScopedServletUtils
230: .getOuterServletRequest(request));
231:
232: // Remove any deferred storage attributes.
233: Handlers.get(getServletContext()).getStorageHandler()
234: .dropChanges(context);
235:
236: // Clear all caches of methods, etc.
237: ClassLevelCache.clearAll();
238:
239: // Clear out all registered modules from the ActionServlet.
240: ActionServlet actionServlet = InternalUtils
241: .getActionServlet(getServletContext());
242:
243: if (actionServlet instanceof AutoRegisterActionServlet) {
244: ((AutoRegisterActionServlet) actionServlet)
245: .clearRegisteredModules();
246: }
247:
248: // Bounce the classloader.
249: init(getConfig(), getPreviousHandler(),
250: getServletContext());
251: }
252: }
253: }
254:
255: private void removeSessionAttributes(ServletRequest request) {
256: HttpSession session = InternalUtils.getHttpSession(request,
257: false);
258:
259: if (session != null) {
260: ArrayList attrsToRemove = new ArrayList();
261:
262: for (Enumeration e = session.getAttributeNames(); e
263: .hasMoreElements();) {
264: String attrName = (String) e.nextElement();
265: Object attr = session.getAttribute(attrName);
266: if (attr.getClass().getClassLoader() == _pageFlowClassLoader) {
267: attrsToRemove.add(attrName);
268: }
269: }
270:
271: for (Iterator i = attrsToRemove.iterator(); i.hasNext();) {
272: String attrName = (String) i.next();
273: if (_log.isDebugEnabled()) {
274: _log
275: .debug("Removing session attribute "
276: + attrName
277: + " because its ClassLoader is being bounced.");
278: }
279:
280: session.removeAttribute(attrName);
281: }
282: }
283: }
284:
285: private void removeRequestAttributes(ServletRequest request) {
286: ArrayList attrsToRemove = new ArrayList();
287:
288: for (Enumeration e = request.getAttributeNames(); e
289: .hasMoreElements();) {
290: String attrName = (String) e.nextElement();
291: Object attr = request.getAttribute(attrName);
292: if (attr.getClass().getClassLoader() == _pageFlowClassLoader) {
293: attrsToRemove.add(attrName);
294: }
295: }
296:
297: for (Iterator i = attrsToRemove.iterator(); i.hasNext();) {
298: String attrName = (String) i.next();
299: if (_log.isDebugEnabled()) {
300: _log.debug("Removing request attribute " + attrName
301: + " because its ClassLoader is being bounced.");
302: }
303:
304: request.removeAttribute(attrName);
305: }
306: }
307:
308: public ClassLoader getClassLoader() {
309: if (_pageFlowClassLoader != null) {
310: synchronized (this ) {
311: return _pageFlowClassLoader;
312: }
313: }
314:
315: return _pageFlowClassLoader;
316: }
317:
318: public boolean isReloadEnabled() {
319: return _enabled;
320: }
321:
322: public ReloadableClassHandler getRegisteredReloadableClassHandler() {
323: return (ReloadableClassHandler) super.getRegisteredHandler();
324: }
325: }
|