What is the ModificationWatcher?
ModificationWatcher is a Wicket class that checks to see if resources have been modified and reloads them without restarting the servlet container (where possible). This is made possible by setting up a polling frequency in the init() method of the WebApplication class for your project. Unfortunately, Google App Engine does not allow the spawning of new threads (on which the ModificationWatcher class depends on), hence a work-around is necessary. First we need to look at the WicketApplication class, as shown in the example below:
- import org.apache.wicket.Application;
- import org.apache.wicket.protocol.http.HttpSessionStore;
- import org.apache.wicket.protocol.http.WebApplication;
- import org.apache.wicket.protocol.http.WebRequest;
- import org.apache.wicket.session.ISessionStore;
- import org.apache.wicket.util.lang.PackageName;
- public class WicketApplication extends WebApplication {
- @Override
- protected void init() {
- super.init();
- // getResourceSettings().setResourcePollFrequency(null);
- }
- protected WebRequest newWebRequest(HttpServletRequest servletRequest) {
- getResourceSettings().getResourceWatcher(true).start(
- getResourceSettings().getResourcePollFrequency());
- return super.newWebRequest(servletRequest);
- }
- @Override
- public String getConfigurationType() {
- return Application.DEVELOPMENT;
- // return Application.DEPLOYMENT;
- }
- @Override
- protected ISessionStore newSessionStore() {
- return new HttpSessionStore(this);
- // return new SecondLevelCacheSessionStore(this, new DiskPageStore());
- }
- }
The first 'hack' to getting Wicket working in GAE is to set the session store type for Wicket to HttpSessionStore, instead of using the SecondLevelCacheSessionStore as the latter directly refers to the file system and is not allowed in the app engine sandbox. This is done by overriding the newSessionStore() method [line 29 in the code extract shown above].
Now, back to the ModificationWatcher. If you want to completely disable it, you can set the poll frequency to null in the init method of your WebApplication class [this has been commented out in line 13 above]. However, if you are in development, this will mean that any changes to the source pages [in my experience its really been the HTML pages], you will have to restart your servlet container to pick-up the changes, which is not very efficient for development purposes. This work-around by-passes the polled watching for file changes and checks to see if the file has changed every time it is requested for. This is achieved by overriding the newWebRequest() method [see line 16 above]. This is fine for development purposes, and MUST be disabled for production, for obvious reasons.
And for this to work, you need a version of the ModificationWatcher class that does not spawn any threads. ModificationWatcher was declared a final class and hence cannot be extended, and so you need to override the Wicket provided version with one of your own. This is easily achieved by creating a similar package structure in your source code and creating the file : org.apache.wicket.util.watch.ModificationWatcher. The code for this class is as below:
- /**
- * Monitors one or more <code>IModifiable</code> objects, calling a
- * {@link IChangeListener IChangeListener} when a given object's modification time changes.
- *
- * @author Jonathan Locke
- * @since 1.2.6
- */
- public final class ModificationWatcher
- {
- /** logger */
- private static final Logger log = LoggerFactory.getLogger(ModificationWatcher.class);
- /** maps <code>IModifiable</code> objects to <code>Entry</code> objects */
- private final Map modifiableToEntry = new ConcurrentHashMap();
- /** the <code>Task</code> to run */
- //private Task task;
- /**
- * Container class for holding modifiable entries to watch.
- */
- private static final class Entry
- {
- // The most recent lastModificationTime polled on the object
- Time lastModifiedTime;
- // The set of listeners to call when the modifiable changes
- final ChangeListenerSet listeners = new ChangeListenerSet();
- // The modifiable thing
- IModifiable modifiable;
- }
- /**
- * Default constructor for two-phase construction.
- */
- public ModificationWatcher()
- {
- }
- /**
- * Constructor that accepts a <code>Duration</code> argument representing the poll frequency.
- *
- * @param pollFrequency
- * how often to check on <code>IModifiable</code>s
- */
- public ModificationWatcher(final Duration pollFrequency)
- {
- start(pollFrequency);
- }
- /**
- * Adds an <code>IModifiable</code> object and an <code>IChangeListener</code> object to
- * call when the modifiable object is modified.
- *
- * @param modifiable
- * an <code>IModifiable</code> object to monitor
- * @param listener
- * an <code>IChangeListener</code> to call if the <code>IModifiable</code> object
- * is modified
- * @return <code>true</code> if the set did not already contain the specified element
- */
- public final boolean add(final IModifiable modifiable, final IChangeListener listener)
- {
- // Look up entry for modifiable
- final Entry entry = (Entry)modifiableToEntry.get(modifiable);
- // Found it?
- if (entry == null)
- {
- if (modifiable.lastModifiedTime() != null)
- {
- // Construct new entry
- final Entry newEntry = new Entry();
- newEntry.modifiable = modifiable;
- newEntry.lastModifiedTime = modifiable.lastModifiedTime();
- newEntry.listeners.add(listener);
- // Put in map
- modifiableToEntry.put(modifiable, newEntry);
- }
- else
- {
- // The IModifiable is not returning a valid lastModifiedTime
- log.info("Cannot track modifications to resource " + modifiable);
- }
- return true;
- }
- else
- {
- // Add listener to existing entry
- return entry.listeners.add(listener);
- }
- }
- /**
- * Removes all entries associated with an <code>IModifiable</code> object.
- *
- * @param modifiable
- * an <code>IModifiable</code> object
- * @return the <code>IModifiable</code> object that was removed, else <code>null</code>
- */
- public IModifiable remove(final IModifiable modifiable)
- {
- final Entry entry = (Entry)modifiableToEntry.remove(modifiable);
- if (entry != null)
- {
- return entry.modifiable;
- }
- return null;
- }
- /**
- * Starts watching at a given <code>Duration</code> polling rate.
- *
- * @param pollFrequency
- * the polling rate <code>Duration</code>
- */
- public void start(final Duration pollFrequency)
- {
- // Construct task with the given polling frequency
- //task = new Task("ModificationWatcher");
- //task.run(pollFrequency, new ICode()
- //{
- // public void run(final Logger log)
- // {
- // Iterate over a copy of the list of entries to avoid
- // concurrent
- // modification problems without the associated liveness issues
- // of holding a lock while potentially polling file times!
- for (final Iterator iterator = new ArrayList(modifiableToEntry.values()).iterator(); iterator
- .hasNext();)
- {
- // Get next entry
- final Entry entry = (Entry)iterator.next();
- // If the modifiable has been modified after the last known
- // modification time
- final Time modifiableLastModified = entry.modifiable.lastModifiedTime();
- if (modifiableLastModified.after(entry.lastModifiedTime))
- {
- // Notify all listeners that the modifiable was modified
- entry.listeners.notifyListeners();
- // Update timestamp
- entry.lastModifiedTime = modifiableLastModified;
- }
- }
- // }
- //});
- }
- /**
- * Stops this <code>ModificationWatcher</code>.
- */
- public void destroy()
- {
- //if (task != null)
- //{
- // task.stop();
- // task.interrupt();
- //}
- }
- /**
- * Retrieves a key set of all <code>IModifiable</code> objects currently being monitored.
- *
- * @return a <code>Set</code> of all <code>IModifiable</code> entries currently maintained
- */
- public final Set getEntries()
- {
- return modifiableToEntry.keySet();
- }
- }
All that has been done is that the Threading references have been taken out so no new threads are spawned and the check for the resources is done in the same thread as that the one that is servicing the user request. This is not the most elegant solution, but it works.
This hack is based on the following sources:
1 comment:
The resource modification watching will be fixed as of Wicket 1.4.0-RC6. The Wicket guys introduced a replaceable IModificationWatcher interface.
https://issues.apache.org/jira/browse/WICKET-2340
Post a Comment