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 */ 017package org.apache.wicket.protocol.http; 018 019import java.net.URL; 020 021import jakarta.servlet.FilterConfig; 022import jakarta.servlet.ServletException; 023 024import org.apache.wicket.Session; 025import org.apache.wicket.application.ReloadingClassLoader; 026import org.apache.wicket.util.listener.IChangeListener; 027 028 029/** 030 * Custom {@link WicketFilter} that reloads the web applications when classes are modified. In order 031 * to monitor changes to your own classes, subclass {@link ReloadingWicketFilter} and use include 032 * and exclude patterns using wildcards. And in web.xml, point to your custom 033 * {@link ReloadingWicketFilter} 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 * <b>NOTE</b> If you wish to reload <em>com.company.search.Form</em>, you have to make sure to 062 * include all classes that <b>reference</b> <em>com.company.search.Form</em>. In particular, if it 063 * is referenced in com.company.Application, you will also have to include the latter. And this is 064 * viral, as for every class you include, you must check that all classes that reference it are also 065 * included. 066 * </p> 067 * 068 * <p> 069 * It is also possible to add an extra URL to watch for changes using 070 * <em>ReloadingClassLoader.addLocation()</em> . By default, all the URLs returned by the parent 071 * class loader (ie all {@link URL} returned by {@link ClassLoader#getResources(String)} with empty 072 * {@link String}) are registered. 073 * </p> 074 * <hr> 075 * <p> 076 * <b>Important. </b> It can be quite tricky to setup the reloading mechanism correctly. Here are 077 * the general guidelines: 078 * </p> 079 * <ul> 080 * <li>The order of include and exclude patterns is significant, the patterns are processed 081 * sequentially in the order they are defined</li> 082 * <li>Don't forget that inner classes are named after YourClass$1, so take that into account when 083 * setting up the patterns, eg include <tt>YourClass*</tt>, not just <tt>YourClass</tt></li> 084 * <li>To enable back-button support for the reloading mechanism, be sure to put 085 * <em>Objects.setObjectStreamFactory(new WicketObjectStreamFactory());</em> in your application's 086 * {@link WebApplication#init()} method. <b>Native JDK object serialization will break the reloading 087 * mechanism when navigating in the browser's history.</b></li> 088 * <li>It is advisable to <b>exclude</b> subclasses of {@link Session} from the the reloading 089 * classloader, because this is the only object that remains across application restarts</li> 090 * <li>Last but not least, make sure to clear your session cookie before reloading the application 091 * home page (or any other bookmarkable page) to get rid of old pages stored in session</li> 092 * </ul> 093 * 094 * <p> 095 * <b>Be sure to carefully read the following information if you also use Spring:</b> 096 * </p> 097 * 098 * <p> 099 * When using org.apache.wicket.spring.SpringWebApplicationFactory, the application must be a bean 100 * with "prototype" scope and the "applicationBean" init parameter must be explicitly set, otherwise 101 * the reloading mechanism won't be able to recreate the application. 102 * </p> 103 * 104 * <p> 105 * <b>WARNING. </b> Be careful that when using Spring or other component managers, you will get 106 * <em>ClassCastException</em> if a given class is loaded two times, one time by the normal 107 * classloader, and another time by the reloading classloader. You need to ensure that your Spring 108 * beans are properly excluded from the reloading class loader and that only the Wicket components 109 * are included. When getting a cryptic error with regard to class loading, class instantiation or 110 * class comparison, first <b>disable the reloading class loader</b> to rule out the possibility of 111 * a classloader conflict. Please keep in mind that two classes are equal if and only if they have 112 * the same name <b>and are loaded in the same classloader</b>. Same goes for errors like 113 * <em>LinkageError: Class FooBar violates loader constraints</em>, better be safe and disable the 114 * reloading feature. 115 * </p> 116 * 117 * @see WicketFilter 118 * 119 * @author <a href="mailto:[email protected]">Jean-Baptiste Quenot</a> 120 */ 121public class ReloadingWicketFilter extends WicketFilter 122{ 123 private ReloadingClassLoader reloadingClassLoader; 124 125 /** 126 * Instantiate the reloading class loader 127 */ 128 public ReloadingWicketFilter() 129 { 130 // Create a reloading classloader 131 reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader()); 132 } 133 134 /** 135 * @see org.apache.wicket.protocol.http.WicketFilter#getClassLoader() 136 */ 137 @Override 138 protected ClassLoader getClassLoader() 139 { 140 return reloadingClassLoader; 141 } 142 143 /** 144 * @see org.apache.wicket.protocol.http.WicketFilter#init(boolean, jakarta.servlet.FilterConfig) 145 */ 146 @Override 147 public void init(final boolean isServlet, final FilterConfig filterConfig) 148 throws ServletException 149 { 150 reloadingClassLoader.setListener(new IChangeListener<Class<?>>() 151 { 152 @Override 153 public void onChange(Class<?> clz) 154 { 155 destroy(); 156 157 // Remove the ModificationWatcher from the current reloading class loader 158 reloadingClassLoader.destroy(); 159 160 /* 161 * Create a new classloader, as there is no way to clear a ClassLoader's cache. This 162 * supposes that we don't share objects across application instances, this is almost 163 * true, except for Wicket's Session object. 164 */ 165 reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader()); 166 try 167 { 168 init(filterConfig); 169 } 170 catch (ServletException e) 171 { 172 throw new RuntimeException(e); 173 } 174 } 175 }); 176 177 super.init(isServlet, filterConfig); 178 } 179}