// Copyright (C) 2014 The Android Open Source Project // // Licensed 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 com.googlesource.gerrit.plugins.gitblit; import java.security.SecureRandom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.inject.Inject; import com.google.inject.Singleton; @Singleton public class PluginActivator implements LifecycleListener { private static final Logger log = LoggerFactory.getLogger(PluginActivator.class); private final String pluginName; private final GerritWicketFilter filter; @Inject public PluginActivator(@PluginName String pluginName, GerritWicketFilter filter) { this.pluginName = pluginName; this.filter = filter; // Just some string that is unique per plugin instance. This is used ultimately in // GerritGitBlitWebApp.newRequestCycleProcessor to expunge stale Java objects attached to the // HTTP session by Wicket. They become "stale" in a plugin reload, because they will have // been loaded by the class loader of the unloaded plugin instance, but then are accessed // by the new plugin instance, which has a new classloader. The result is funny // ClassCastExceptions telling you that "GitBlitWebSession cannot be cast to Session" even // though, if you look at the source code, GitBlitWebSession very clearly is derived from // Session. I think I understand why the GitBlit author, James Moger, has said several times // he finds Wicket's stateful model a pain. filter.setPluginInstanceKey(Long.toHexString(new SecureRandom().nextLong()) + Long.toHexString(System.nanoTime()) + Long.toHexString(System.currentTimeMillis())); } @Override public void start() { // Just so that we can see in the log whether this activator is invoked. log.info("Starting plugin {}", pluginName); } @Override public void stop() { log.info("Stopping plugin {}", pluginName); // Wicket internally keeps a number of session-related things around. To support clean Gerrit plugin reloading, // we must be sure that this data survives in proper form. The main problem here is that the HTTP Session is // kept across the plugin reload, and Wicket stores stuff keyed by sessionId. The call below ultimately will // serialize Wicket's per-session page state to disk, where it will be found again when the new plugin instance // starts up. Since the data is serialized and deserialized, this works even if the class instances change. filter.destroy(); // Note that Wicket also stores some unserialized Java objects directly in the HTPP Session. That causes // ClassCastExceptions if the newly started plugin instance then tries to retrieve them, because they have // been loaded by a different classloader (the one of the previous plugin instance). There's two problems // with that: // - we don't have access to any session here, and I have not found any way to enumerate all sessions known // to Wicket. // - even if we somehow could access these sessions, I've not found any clean way to tell Wicket that it // should serialize its Java objects attached to that session, and de-serialize them again when the new // plugin instance has started and comes across such a HTTP session again. // Therefore, we have to deal either in the GerritWicketFilter or in the request cycle processor with possibly // stale orphaned Java objects attached to the HTTP session. See GerritGitBlitWebApp.newRequestCycleProcessor. log.info("Filter destroyed {}", pluginName); } }