// Copyright (C) 2009 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.google.gerrit.server.http;

import static com.google.inject.Stage.PRODUCTION;

import com.google.gerrit.git.PushAllProjectsOp;
import com.google.gerrit.git.ReloadSubmitQueueOp;
import com.google.gerrit.git.WorkQueue;
import com.google.gerrit.server.cache.CachePool;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.DatabaseModule;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.ssh.SshDaemon;
import com.google.gerrit.server.ssh.SshModule;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.spi.Message;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContextEvent;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;

/** Configures the web application environment for Gerrit Code Review. */
public class GerritServletConfig extends GuiceServletContextListener {
  private static final Logger log =
      LoggerFactory.getLogger(GerritServletConfig.class);

  private Injector dbInjector;
  private Injector sysInjector;
  private Injector webInjector;
  private Injector sshInjector;

  private synchronized void init() {
    if (sysInjector == null) {
      try {
        dbInjector = Guice.createInjector(PRODUCTION, new DatabaseModule());
      } catch (CreationException ce) {
        final Message first = ce.getErrorMessages().iterator().next();
        final StringBuilder buf = new StringBuilder();
        buf.append(first.getMessage());
        Throwable why = first.getCause();
        while (why != null) {
          buf.append("\n  caused by ");
          buf.append(why.toString());
          why = why.getCause();
        }
        if (first.getCause() != null) {
          buf.append("\n");
          buf.append("\nResolve above errors before continuing.");
          buf.append("\nComplete stack trace follows:");
        }
        log.error(buf.toString(), first.getCause());
        throw new CreationException(Collections.singleton(first));
      }

      sysInjector = GerritGlobalModule.createInjector(dbInjector);
      sshInjector = createSshInjector();
      webInjector = createWebInjector();

      // Push the Provider<HttpServletRequest> down into the canonical
      // URL provider. Its optional for that provider, but since we can
      // supply one we should do so, in case the administrator has not
      // setup the canonical URL in the configuration file.
      //
      // Note we have to do this manually as Guice failed to do the
      // injection here because the HTTP environment is not visible
      // to the core server modules.
      //
      sysInjector.getInstance(CanonicalWebUrlProvider.class)
          .setHttpServletRequest(
              webInjector.getProvider(HttpServletRequest.class));
    }
  }

  private Injector createSshInjector() {
    return sysInjector.createChildInjector(new SshModule());
  }

  private Injector createWebInjector() {
    final List<Module> modules = new ArrayList<Module>();
    modules.add(sshInjector.getInstance(WebModule.class));
    return sysInjector.createChildInjector(modules);
  }

  @Override
  protected Injector getInjector() {
    init();
    return webInjector;
  }

  @Override
  public void contextInitialized(final ServletContextEvent event) {
    super.contextInitialized(event);
    init();

    try {
      sysInjector.getInstance(CachePool.class).start();
    } catch (ConfigurationException e) {
      log.error("Unable to start CachePool", e);
    } catch (ProvisionException e) {
      log.error("Unable to start CachePool", e);
    }

    try {
      sysInjector.getInstance(PushAllProjectsOp.Factory.class).create(null)
          .start(30, TimeUnit.SECONDS);
    } catch (ConfigurationException e) {
      log.error("Unable to restart replication queue", e);
    } catch (ProvisionException e) {
      log.error("Unable to restart replication queue", e);
    }

    try {
      sysInjector.getInstance(ReloadSubmitQueueOp.Factory.class).create()
          .start(15, TimeUnit.SECONDS);
    } catch (ConfigurationException e) {
      log.error("Unable to restart merge queue", e);
    } catch (ProvisionException e) {
      log.error("Unable to restart merge queue", e);
    }

    try {
      sshInjector.getInstance(SshDaemon.class).start();
    } catch (ConfigurationException e) {
      log.error("Unable to start SSHD", e);
    } catch (ProvisionException e) {
      log.error("Unable to start SSHD", e);
    } catch (IOException e) {
      log.error("Unable to start SSHD", e);
    }
  }

  @Override
  public void contextDestroyed(final ServletContextEvent event) {
    try {
      if (sshInjector != null) {
        sshInjector.getInstance(SshDaemon.class).stop();
      }
    } catch (ConfigurationException e) {
    } catch (ProvisionException e) {
    }

    try {
      if (sysInjector != null) {
        sysInjector.getInstance(WorkQueue.class).shutdown();
      }
    } catch (ConfigurationException e) {
    } catch (ProvisionException e) {
    }

    try {
      if (sysInjector != null) {
        sysInjector.getInstance(CachePool.class).stop();
      }
    } catch (ConfigurationException e) {
    } catch (ProvisionException e) {
    }

    try {
      if (dbInjector != null) {
        closeDataSource(dbInjector.getInstance(DatabaseModule.DS));
      }
    } catch (ConfigurationException ce) {
    } catch (ProvisionException ce) {
    }

    super.contextDestroyed(event);
  }

  private void closeDataSource(final DataSource ds) {
    try {
      final Class<?> type = Class.forName("com.mchange.v2.c3p0.DataSources");
      if (type.isInstance(ds)) {
        type.getMethod("destroy", DataSource.class).invoke(null, ds);
        return;
      }
    } catch (Throwable bad) {
      // Oh well, its not a c3p0 pooled connection. Too bad its
      // not standardized how "good applications cleanup".
    }
  }
}

@@ -6584,21 +6584,303 @@
 %7B%0A      
-final
+Class%3C?%3E type = Class.forName(%22org.apache.commons.dbcp.BasicDataSource%22);%0A      if (type.isInstance(ds)) %7B%0A        type.getMethod(%22close%22).invoke(ds);%0A        return;%0A      %7D%0A    %7D catch (Throwable bad) %7B%0A      // Oh well, its not a Commons DBCP pooled connection.%0A    %7D%0A%0A    try %7B%0A     
  Class%3C?
@@ -7087,32 +7087,32 @@
 hrowable bad) %7B%0A
+
       // Oh well
@@ -7150,79 +7150,8 @@
 ion.
- Too bad its%0A      // not standardized how %22good applications cleanup%22.
 %0A   

Support cleaning up a Commons DBCP connection pool