/*
 * 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.
 */


#include "pagespeed/apache/apache_rewrite_driver_factory.h"

#include <unistd.h>

#include "pagespeed/apache/apache_httpd_includes.h"
#include "apr_pools.h"
#include "ap_mpm.h"

#include "base/logging.h"
#include "pagespeed/apache/apache_config.h"
#include "pagespeed/apache/apache_message_handler.h"
#include "pagespeed/apache/apache_server_context.h"
#include "pagespeed/apache/apache_thread_system.h"
#include "pagespeed/apache/apr_timer.h"
#include "net/instaweb/rewriter/public/rewrite_driver_factory.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/null_shared_mem.h"
#include "pagespeed/kernel/base/stl_util.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/sharedmem/shared_circular_buffer.h"
#include "pagespeed/kernel/thread/pthread_shared_mem.h"
#include "pagespeed/kernel/thread/scheduler_thread.h"
#include "pagespeed/kernel/thread/slow_worker.h"
#include "pagespeed/system/controller_manager.h"

namespace net_instaweb {

class ProcessContext;

ApacheRewriteDriverFactory::ApacheRewriteDriverFactory(
    const ProcessContext& process_context,
    server_rec* server, const StringPiece& version)
    : SystemRewriteDriverFactory(
          process_context,
          new ApacheThreadSystem,
          NULL, /* default shared memory runtime */
          server->server_hostname,
          server->port),
      server_rec_(server),
      scheduler_thread_(nullptr),
      version_(version.data(), version.size()),
      apache_message_handler_(new ApacheMessageHandler(
          server_rec_, version_, timer(), thread_system()->NewMutex())),
      apache_html_parse_message_handler_(new ApacheMessageHandler(
          server_rec_, version_, timer(), thread_system()->NewMutex())) {
  apr_pool_create(&pool_, NULL);

  // Apache defaults UsePerVhostStatistics to false for historical reasons, but
  // more recent implementations default it to true.
  set_use_per_vhost_statistics(false);

  // Make sure the ownership of apache_message_handler_ and
  // apache_html_parse_message_handler_ is given to scoped pointer.
  // Otherwise may result in leak error in test.
  message_handler();
  html_parse_message_handler();
  InitializeDefaultOptions();
}

ApacheRewriteDriverFactory::~ApacheRewriteDriverFactory() {
  // We free all the resources before destroying the pool, because some of the
  // resource uses the sub-pool and will need that pool to be around to
  // clean up properly.
  ShutDown();

  apr_pool_destroy(pool_);

  // We still have registered a pool deleter here, right?  This seems risky...
  STLDeleteElements(&uninitialized_server_contexts_);

  // Apache startup is pretty weird, in that it initializes twice:
  // first to check configuration, then for real. In between the two runs,
  // it cleans us up very thoroughly, including unloading our module, so if we
  // are here at the end of run 1, we are about to forget all about the
  // controller process hanging around, while the FD to it will be kept around
  // (including accross daemonization), keeping it alive.
  //
  // So here we drop the FD, to get the controller to exit, letting us start
  // it again (and we want it to exit on regular exit, too).
  //
  // This call is a no-op if nothing was started.
  //
  // This is done in Apache-specific code rather than System* because
  // nginx has other challenges: it can create multiple
  // SystemRewriteDriverFactory's at once when reloading config, and
  // ~SystemRewriteDriverFactory for the old one happens too late to be useful,
  // so there we are better off just using global state to keep track of
  // the controller (as there are no pesky dlunload's making us forget all of
  // it!).
  ControllerManager::DetachFromControllerProcess();
}

Timer* ApacheRewriteDriverFactory::DefaultTimer() {
  return new AprTimer();
}

MessageHandler* ApacheRewriteDriverFactory::DefaultHtmlParseMessageHandler() {
  return apache_html_parse_message_handler_;
}

MessageHandler* ApacheRewriteDriverFactory::DefaultMessageHandler() {
  return apache_message_handler_;
}

void ApacheRewriteDriverFactory::SetupCaches(ServerContext* server_context) {
  SystemRewriteDriverFactory::SetupCaches(server_context);

  // TODO(jmarantz): It would make more sense to have the base ServerContext
  // own the ProxyFetchFactory, but that would create a cyclic directory
  // dependency.  This can be resolved minimally by moving proxy_fetch.cc
  // from automatic/ to rewriter/.  I think we should also think harder about
  // separating out rewriting infrastructure from rewriters.
  ApacheServerContext* apache_server_context =
      dynamic_cast<ApacheServerContext*>(server_context);
  CHECK(apache_server_context != NULL);
  apache_server_context->InitProxyFetchFactory();
}

void ApacheRewriteDriverFactory::SetNeedSchedulerThread() {
  if (scheduler_thread_ == nullptr) {
    scheduler_thread_ = new SchedulerThread(thread_system(), scheduler());
    defer_cleanup(scheduler_thread_->MakeDeleter());
    scheduler_thread_->Start();
  }
}

bool ApacheRewriteDriverFactory::IsServerThreaded() {
  // Detect whether we're using a threaded MPM.
  apr_status_t status;
  int result = 0, threads = 1;
  status = ap_mpm_query(AP_MPMQ_IS_THREADED, &result);
  if (status == APR_SUCCESS &&
      (result == AP_MPMQ_STATIC || result == AP_MPMQ_DYNAMIC)) {
    // Number of configured threads.
    status = ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads);
    if (status != APR_SUCCESS) {
      return false;  // Assume non-thready by default.
    }
  }

  return threads > 1;
}

int ApacheRewriteDriverFactory::LookupThreadLimit() {
  int thread_limit = 0;
  // The compiled maximum number of threads.
  ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
  return thread_limit;
}

void ApacheRewriteDriverFactory::ParentOrChildInit() {
  if (install_crash_handler()) {
    ApacheMessageHandler::InstallCrashHandler(server_rec_);
  }
  SystemRewriteDriverFactory::ParentOrChildInit();
}

void ApacheRewriteDriverFactory::ShutDownMessageHandlers() {
  // Reset SharedCircularBuffer to NULL, so that any shutdown warnings
  // (e.g. in ServerContext::ShutDownDrivers) don't reference
  // deleted objects as the base-class is deleted.
  //
  // TODO(jefftk): merge ApacheMessageHandler and NgxMessageHandler into
  // SystemMessageHandler and then move this into System.
  apache_message_handler_->set_buffer(NULL);
  apache_html_parse_message_handler_->set_buffer(NULL);
}

void ApacheRewriteDriverFactory::SetupMessageHandlers() {
  // TODO(jefftk): merge ApacheMessageHandler and NgxMessageHandler into
  // SystemMessageHandler and then move this into System.
  apache_message_handler_->SetPidString(static_cast<int64>(getpid()));
  apache_html_parse_message_handler_->SetPidString(
            static_cast<int64>(getpid()));
}

void ApacheRewriteDriverFactory::SetCircularBuffer(
    SharedCircularBuffer* buffer) {
  // TODO(jefftk): merge ApacheMessageHandler and NgxMessageHandler into
  // SystemMessageHandler and then move this into System.
  apache_message_handler_->set_buffer(buffer);
  apache_html_parse_message_handler_->set_buffer(buffer);
}

void ApacheRewriteDriverFactory::Initialize() {
  ApacheConfig::Initialize();
  RewriteDriverFactory::Initialize();
}

void ApacheRewriteDriverFactory::InitStats(Statistics* statistics) {
  // Init standard system stats.
  SystemRewriteDriverFactory::InitStats(statistics);

  // Init Apache-specific stats.
  ApacheServerContext::InitStats(statistics);
}

void ApacheRewriteDriverFactory::Terminate() {
  RewriteDriverFactory::Terminate();
  ApacheConfig::Terminate();
  PthreadSharedMem::Terminate();
}

ApacheServerContext* ApacheRewriteDriverFactory::MakeApacheServerContext(
    server_rec* server) {
  ApacheServerContext* server_context =
      new ApacheServerContext(this, server, version_);
  uninitialized_server_contexts_.insert(server_context);
  return server_context;
}

ServerContext* ApacheRewriteDriverFactory::NewDecodingServerContext() {
  ServerContext* sc = new ApacheServerContext(this, server_rec_, version_);
  InitStubDecodingServerContext(sc);
  return sc;
}

bool ApacheRewriteDriverFactory::PoolDestroyed(
    ApacheServerContext* server_context) {
  if (uninitialized_server_contexts_.erase(server_context) == 1) {
    delete server_context;
  }

  // Returns true if all the ServerContexts known by the factory and its
  // superclass are finished.  Then it's time to destroy the factory.  Note
  // that ApacheRewriteDriverFactory keeps track of ServerContexts that
  // are partially constructed.  RewriteDriverFactory keeps track of
  // ServerContexts that are already serving requests.  We need to clean
  // all of them out before we can terminate the driver.
  bool no_active_server_contexts = TerminateServerContext(server_context);
  return (no_active_server_contexts && uninitialized_server_contexts_.empty());
}

ApacheConfig* ApacheRewriteDriverFactory::NewRewriteOptions() {
  return new ApacheConfig(hostname_identifier(), thread_system());
}

ApacheConfig* ApacheRewriteDriverFactory::NewRewriteOptionsForQuery() {
  return new ApacheConfig("query", thread_system());
}

}  // namespace net_instaweb
