/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.protocol.http;

import java.net.URL;

import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;

import org.apache.wicket.Session;
import org.apache.wicket.application.ReloadingClassLoader;
import org.apache.wicket.util.listener.IChangeListener;


/**
 * Custom {@link WicketFilter} that reloads the web applications when classes are modified. In order
 * to monitor changes to your own classes, subclass {@link ReloadingWicketFilter} and use include
 * and exclude patterns using wildcards. And in web.xml, point to your custom
 * {@link ReloadingWicketFilter} instead of the original {@link WicketFilter}.
 * 
 * <p>
 * The built-in patterns are:
 * </p>
 * 
 * <pre>
 * ReloadingClassLoader.excludePattern(&quot;org.apache.wicket.*&quot;);
 * ReloadingClassLoader.includePattern(&quot;org.apache.wicket.examples.*&quot;);
 * </pre>
 * 
 * <p>
 * <b>Example. </b> Defining custom patterns
 * </p>
 * 
 * <pre>
 * public class MyReloadingFilter extends ReloadingWicketFilter
 * {
 * 	static
 * 	{
 * 		ReloadingClassLoader.includePattern(&quot;com.company.*&quot;);
 * 		ReloadingClassLoader.excludePattern(&quot;com.company.spring.beans.*&quot;);
 * 		ReloadingClassLoader.includePattern(&quot;some.other.package.with.wicket.components.*&quot;);
 * 	}
 * }
 * </pre>
 * 
 * <p>
 * <b>NOTE</b> If you wish to reload <em>com.company.search.Form</em>, you have to make sure to
 * include all classes that <b>reference</b> <em>com.company.search.Form</em>. In particular, if it
 * is referenced in com.company.Application, you will also have to include the latter. And this is
 * viral, as for every class you include, you must check that all classes that reference it are also
 * included.
 * </p>
 * 
 * <p>
 * It is also possible to add an extra URL to watch for changes using
 * <em>ReloadingClassLoader.addLocation()</em> . By default, all the URLs returned by the parent
 * class loader (ie all {@link URL} returned by {@link ClassLoader#getResources(String)} with empty
 * {@link String}) are registered.
 * </p>
 * <hr>
 * <p>
 * <b>Important. </b> It can be quite tricky to setup the reloading mechanism correctly. Here are
 * the general guidelines:
 * </p>
 * <ul>
 * <li>The order of include and exclude patterns is significant, the patterns are processed
 * sequentially in the order they are defined</li>
 * <li>Don't forget that inner classes are named after YourClass$1, so take that into account when
 * setting up the patterns, eg include <tt>YourClass*</tt>, not just <tt>YourClass</tt></li>
 * <li>To enable back-button support for the reloading mechanism, be sure to put
 * <em>Objects.setObjectStreamFactory(new WicketObjectStreamFactory());</em> in your application's
 * {@link WebApplication#init()} method. <b>Native JDK object serialization will break the reloading
 * mechanism when navigating in the browser's history.</b></li>
 * <li>It is advisable to <b>exclude</b> subclasses of {@link Session} from the the reloading
 * classloader, because this is the only object that remains across application restarts</li>
 * <li>Last but not least, make sure to clear your session cookie before reloading the application
 * home page (or any other bookmarkable page) to get rid of old pages stored in session</li>
 * </ul>
 * 
 * <p>
 * <b>Be sure to carefully read the following information if you also use Spring:</b>
 * </p>
 * 
 * <p>
 * When using org.apache.wicket.spring.SpringWebApplicationFactory, the application must be a bean
 * with "prototype" scope and the "applicationBean" init parameter must be explicitly set, otherwise
 * the reloading mechanism won't be able to recreate the application.
 * </p>
 * 
 * <p>
 * <b>WARNING. </b> Be careful that when using Spring or other component managers, you will get
 * <em>ClassCastException</em> if a given class is loaded two times, one time by the normal
 * classloader, and another time by the reloading classloader. You need to ensure that your Spring
 * beans are properly excluded from the reloading class loader and that only the Wicket components
 * are included. When getting a cryptic error with regard to class loading, class instantiation or
 * class comparison, first <b>disable the reloading class loader</b> to rule out the possibility of
 * a classloader conflict. Please keep in mind that two classes are equal if and only if they have
 * the same name <b>and are loaded in the same classloader</b>. Same goes for errors like
 * <em>LinkageError: Class FooBar violates loader constraints</em>, better be safe and disable the
 * reloading feature.
 * </p>
 * 
 * @see WicketFilter
 * 
 * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a>
 */
public class ReloadingWicketFilter extends WicketFilter
{
	private ReloadingClassLoader reloadingClassLoader;

	/**
	 * Instantiate the reloading class loader
	 */
	public ReloadingWicketFilter()
	{
		// Create a reloading classloader
		reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader());
	}

	/**
	 * @see org.apache.wicket.protocol.http.WicketFilter#getClassLoader()
	 */
	@Override
	protected ClassLoader getClassLoader()
	{
		return reloadingClassLoader;
	}

	/**
	 * @see org.apache.wicket.protocol.http.WicketFilter#init(boolean, jakarta.servlet.FilterConfig)
	 */
	@Override
	public void init(final boolean isServlet, final FilterConfig filterConfig)
		throws ServletException
	{
		reloadingClassLoader.setListener(new IChangeListener<Class<?>>()
		{
			@Override
			public void onChange(Class<?> clz)
			{
				destroy();

				// Remove the ModificationWatcher from the current reloading class loader
				reloadingClassLoader.destroy();

				/*
				 * Create a new classloader, as there is no way to clear a ClassLoader's cache. This
				 * supposes that we don't share objects across application instances, this is almost
				 * true, except for Wicket's Session object.
				 */
				reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader());
				try
				{
					init(filterConfig);
				}
				catch (ServletException e)
				{
					throw new RuntimeException(e);
				}
			}
		});

		super.init(isServlet, filterConfig);
	}
}
