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: package org.apache.wicket.protocol.http;
018:
019: import java.net.URL;
020:
021: import javax.servlet.FilterConfig;
022: import javax.servlet.ServletException;
023:
024: import org.apache.wicket.Session;
025: import org.apache.wicket.application.ReloadingClassLoader;
026: import org.apache.wicket.util.listener.IChangeListener;
027:
028: /**
029: * Custom {@link WicketFilter} that reloads the web applications when classes
030: * are modified. In order to monitor changes to your own classes, subclass
031: * {@link ReloadingWicketFilter} and use include and exclude patterns using
032: * wildcards. And in web.xml, point to your custom {@link ReloadingWicketFilter}
033: * instead of the original {@link WicketFilter}.
034: *
035: * <p>
036: * The built-in patterns are:
037: * </p>
038: *
039: * <pre>
040: * ReloadingClassLoader.excludePattern("org.apache.wicket.*");
041: * ReloadingClassLoader.includePattern("org.apache.wicket.examples.*");
042: * </pre>
043: *
044: * <p>
045: * <b>Example. </b> Defining custom patterns
046: * </p>
047: *
048: * <pre>
049: * public class MyReloadingFilter extends ReloadingWicketFilter
050: * {
051: * static
052: * {
053: * ReloadingClassLoader.includePattern("com.company.*");
054: * ReloadingClassLoader.excludePattern("com.company.spring.beans.*");
055: * ReloadingClassLoader.includePattern("some.other.package.with.wicket.components.*");
056: * }
057: * }
058: * </pre>
059: *
060: * <p>
061: * It is also possible to add an extra URL to watch for changes using
062: * <tt>ReloadingClassLoader.addLocation()</tt> . By default, all the URLs
063: * returned by the parent class loader (ie all {@link URL} returned by
064: * {@link ClassLoader#getResources(String)} with empty {@link String}) are
065: * registered.
066: * </p>
067: * <hr>
068: * <p>
069: * <b>Important. </b> It can be quite tricky to setup the reloading mechanism
070: * correctly. Here are the general guidelines:
071: * </p>
072: * <ul>
073: * <li>The order of include and exclude patterns is significant, the patterns
074: * are processed sequentially in the order they are defined</li>
075: * <li>Don't forget that inner classes are named after YourClass$1, so take
076: * that into account when setting up the patterns, eg include
077: * <tt>YourClass*</tt>, not just <tt>YourClass</tt></li>
078: * <li>To enable back-button support for the reloading mechanism, be sure to
079: * put <tt>Objects.setObjectStreamFactory(new WicketObjectStreamFactory());</tt>
080: * in your application's {@link WebApplication#init()} method. <b>Native JDK
081: * object serialization will break the reloading mechanism when navigating in
082: * the browser's history.</b></li>
083: * <li>It is advisable to <b>exclude</b> subclasses of {@link Session} from
084: * the the reloading classloader, because this is the only object that remains
085: * across application restarts</li>
086: * </ul>
087: *
088: * <p>
089: * <b>Be sure to carefully read the following information if you also use
090: * Spring:</b>
091: * </p>
092: *
093: * <p>
094: * When using Spring, the application must not be a Spring bean itself,
095: * otherwise the reloading mechanism won't be able to reload the application. In
096: * particular, make sure <b>not</b> to use
097: * org.apache.wicket.spring.SpringWebApplicationFactory in web.xml. If you
098: * really need to inject dependencies in your application, use
099: * DefaultListableBeanFactory.autowireBeanProperties() in the init() method.
100: * </p>
101: *
102: * <p>
103: * <b>WARNING. </b> Be careful that when using Spring or other component
104: * managers, you will get <tt>ClassCastException</tt> if a given class is
105: * loaded two times, one time by the normal classloader, and another time by the
106: * reloading classloader. You need to ensure that your Spring beans are properly
107: * excluded from the reloading class loader and that only the Wicket components
108: * are included. When getting a cryptic error with regard to class loading,
109: * class instantiation or class comparison, first <b>disable the reloading class
110: * loader</b> to rule out the possibility of a classloader conflict. Please
111: * keep in mind that two classes are equal if and only if they have the same
112: * name <b>and are loaded in the same classloader</b>. Same goes for errors
113: * like <tt>LinkageError: Class FooBar violates loader constraints</tt>,
114: * better be safe and disable the reloading feature.
115: * </p>
116: *
117: * @see WicketFilter
118: *
119: * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a>
120: */
121: public class ReloadingWicketFilter extends WicketFilter {
122: private ReloadingClassLoader reloadingClassLoader;
123:
124: /**
125: * Instantiate the reloading class loader
126: */
127: public ReloadingWicketFilter() {
128: // Create a reloading classloader
129: reloadingClassLoader = new ReloadingClassLoader(getClass()
130: .getClassLoader());
131: }
132:
133: /**
134: * @see org.apache.wicket.protocol.http.WicketFilter#getClassLoader()
135: */
136: protected ClassLoader getClassLoader() {
137: return reloadingClassLoader;
138: }
139:
140: /**
141: * @see org.apache.wicket.protocol.http.WicketFilter#init(javax.servlet.FilterConfig)
142: */
143: public void init(final FilterConfig filterConfig)
144: throws ServletException {
145: reloadingClassLoader.setListener(new IChangeListener() {
146: public void onChange() {
147: /*
148: * Create a new classloader, as there is no way to clear a
149: * ClassLoader's cache. This supposes that we don't share
150: * objects across application instances, this is almost true,
151: * except for Wicket's Session object.
152: */
153: reloadingClassLoader = new ReloadingClassLoader(
154: getClass().getClassLoader());
155: try {
156: init(filterConfig);
157: } catch (ServletException e) {
158: throw new RuntimeException(e);
159: }
160: }
161: });
162:
163: super.init(filterConfig);
164: }
165: }
|