// Copyright (C) 2015 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.httpd.plugins;

import static javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED;

import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.plugins.Plugin;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.servlet.GuiceFilter;

import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.FilterChain;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Singleton
public class LfsPluginServlet extends HttpServlet
    implements StartPluginListener, ReloadPluginListener {
  private static final long serialVersionUID = 1L;
  private static final Logger log
      = LoggerFactory.getLogger(LfsPluginServlet.class);

  public static final String URL_REGEX =
      "^(?:/a)?(?:/p/|/)(.+)(?:/info/lfs/objects/batch)$";

  private List<Plugin> pending = new ArrayList<>();
  private final String pluginName;
  private final FilterChain chain;
  private AtomicReference<GuiceFilter> filter;

  @Inject
  LfsPluginServlet(@GerritServerConfig Config cfg) {
    this.pluginName = cfg.getString("lfs", null, "plugin");
    this.chain = new FilterChain() {
      @Override
      public void doFilter(ServletRequest req, ServletResponse res)
          throws IOException {
        Resource.NOT_FOUND.send(
            (HttpServletRequest) req, (HttpServletResponse) res);
      }
    };
    this.filter = new AtomicReference<>();
  }

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    if (filter.get() == null) {
      CacheHeaders.setNotCacheable(res);
      res.sendError(SC_NOT_IMPLEMENTED);
      return;
    }
    filter.get().doFilter(req, res, chain);
  }

  @Override
  public synchronized void init(ServletConfig config) throws ServletException {
    super.init(config);

    for (Plugin plugin : pending) {
      install(plugin);
    }
    pending = null;
  }

  @Override
  public synchronized void onStartPlugin(Plugin plugin) {
    if (pending != null) {
      pending.add(plugin);
    } else {
      install(plugin);
    }
  }

  @Override
  public void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin) {
    install(newPlugin);
  }

  private void install(Plugin plugin) {
    if (!plugin.getName().equals(pluginName)) {
      return;
    }
    final GuiceFilter guiceFilter = load(plugin);
    plugin.add(new RegistrationHandle() {
      @Override
      public void remove() {
        filter.compareAndSet(guiceFilter, null);
      }
    });
    filter.set(guiceFilter);
  }

  private GuiceFilter load(Plugin plugin) {
    if (plugin.getHttpInjector() != null) {
      final String name = plugin.getName();
      final GuiceFilter guiceFilter;
      try {
        guiceFilter = plugin.getHttpInjector().getInstance(GuiceFilter.class);
      } catch (RuntimeException e) {
        log.warn(String.format("Plugin %s cannot load GuiceFilter", name), e);
        return null;
      }

      try {
        ServletContext ctx =
            PluginServletContext.create(plugin, "/");
        guiceFilter.init(new WrappedFilterConfig(ctx));
      } catch (ServletException e) {
        log.warn(String.format("Plugin %s failed to initialize HTTP", name), e);
        return null;
      }

      plugin.add(new RegistrationHandle() {
        @Override
        public void remove() {
          guiceFilter.destroy();
        }
      });
      return guiceFilter;
    }
    return null;
  }
}

@@ -644,16 +644,71 @@
 ugins;%0A%0A
+import static java.nio.charset.StandardCharsets.UTF_8;%0A
 import s
@@ -1396,27 +1396,116 @@
 java.io.
-IOException
+BufferedWriter;%0Aimport java.io.IOException;%0Aimport java.io.OutputStreamWriter;%0Aimport java.io.Writer
 ;%0Aimport
@@ -2320,16 +2320,269 @@
 ch)$%22;%0A%0A
+  private static final String CONTENTTYPE_VND_GIT_LFS_JSON =%0A      %22application/vnd.git-lfs+json; charset=utf-8%22;%0A  private static final String MESSAGE_LFS_NOT_CONFIGURED =%0A      %22%7B%5C%22message%5C%22:%5C%22No LFS plugin is configured to handle LFS requests.%5C%22%7D%22;%0A%0A
   privat
@@ -3353,81 +3353,36 @@
     
-CacheHeaders.setNotCacheable(res);%0A      res.sendError(SC_NOT_IMPLEMENTED
+responseLfsNotConfigured(res
 );%0A 
@@ -3927,32 +3927,415 @@
 ewPlugin);%0A  %7D%0A%0A
+  private void responseLfsNotConfigured(HttpServletResponse res)%0A      throws IOException %7B%0A    CacheHeaders.setNotCacheable(res);%0A    res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);%0A    res.setStatus(SC_NOT_IMPLEMENTED);%0A    Writer w = new BufferedWriter(%0A        new OutputStreamWriter(res.getOutputStream(), UTF_8));%0A    w.write(MESSAGE_LFS_NOT_CONFIGURED);%0A    w.flush();%0A  %7D%0A%0A
   private void i

Return meaningful message when LFS plugin is not configured