// 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.git;

import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.server.GerritServer;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;

import com.jcraft.jsch.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spearce.jgit.lib.RepositoryConfig;
import org.spearce.jgit.transport.OpenSshConfig;
import org.spearce.jgit.transport.RefSpec;
import org.spearce.jgit.transport.RemoteConfig;
import org.spearce.jgit.transport.SshConfigSessionFactory;
import org.spearce.jgit.transport.SshSessionFactory;
import org.spearce.jgit.transport.URIish;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class PushQueue {
  static final Logger log = LoggerFactory.getLogger(PushQueue.class);
  private static final int startDelay = 15; // seconds
  private static List<RemoteConfig> configs;
  private static final Map<URIish, PushOp> pending =
      new HashMap<URIish, PushOp>();

  static {
    // Install our own factory which always runs in batch mode, as we
    // have no UI available for interactive prompting.
    //
    SshSessionFactory.setInstance(new SshConfigSessionFactory() {
      @Override
      protected void configure(OpenSshConfig.Host hc, Session session) {
        // Default configuration is batch mode.
      }
    });
  }

  public static void scheduleUpdate(final Project.NameKey project,
      final String ref) {
    for (final RemoteConfig srcConf : allConfigs()) {
      RefSpec spec = null;
      for (final RefSpec s : srcConf.getPushRefSpecs()) {
        if (s.matchSource(ref)) {
          spec = s;
          break;
        }
      }
      if (spec == null) {
        continue;
      }

      for (URIish uri : srcConf.getURIs()) {
        uri = uri.setPath(replace(uri.getPath(), "name", project.get()));
        scheduleImp(project, ref, srcConf, uri);
      }
    }
  }

  private static synchronized void scheduleImp(final Project.NameKey project,
      final String ref, final RemoteConfig srcConf, final URIish uri) {
    PushOp e = pending.get(uri);
    if (e == null) {
      e = new PushOp(project.get(), srcConf, uri);
      WorkQueue.schedule(e, startDelay, TimeUnit.SECONDS);
      pending.put(uri, e);
    }
    e.addRef(ref);
  }

  static synchronized void notifyStarting(final PushOp op) {
    pending.remove(op.getURI());
  }

  private static String replace(final String pat, final String key,
      final String val) {
    final int n = pat.indexOf("${" + key + "}");
    return pat.substring(0, n) + val + pat.substring(n + 3 + key.length());
  }

  private static synchronized List<RemoteConfig> allConfigs() {
    if (configs == null) {
      final File path;
      try {
        final GerritServer gs = GerritServer.getInstance();
        path = gs.getSitePath();
        if (path == null || gs.getRepositoryCache() == null) {
          return Collections.emptyList();
        }
      } catch (OrmException e) {
        return Collections.emptyList();
      } catch (XsrfException e) {
        return Collections.emptyList();
      }

      final File cfgFile = new File(path, "replication.config");
      final RepositoryConfig cfg = new RepositoryConfig(null, cfgFile);
      try {
        cfg.load();
        final ArrayList<RemoteConfig> r = new ArrayList<RemoteConfig>();
        for (final RemoteConfig c : RemoteConfig.getAllRemoteConfigs(cfg)) {
          if (c.getURIs().isEmpty()) {
            continue;
          }

          for (final URIish u : c.getURIs()) {
            if (u.getPath() == null || !u.getPath().contains("${name}")) {
              final String s = u.toString();
              throw new URISyntaxException(s, "No ${name}");
            }
          }

          if (c.getPushRefSpecs().isEmpty()) {
            RefSpec spec = new RefSpec();
            spec = spec.setSourceDestination("refs/*", "refs/*");
            spec = spec.setForceUpdate(true);
            c.addPushRefSpec(spec);
          }

          r.add(c);
        }
        configs = Collections.unmodifiableList(r);
      } catch (FileNotFoundException e) {
        log.warn("No " + cfgFile + "; not replicating");
        configs = Collections.emptyList();
      } catch (IOException e) {
        log.error("Can't read " + cfgFile, e);
        return Collections.emptyList();
      } catch (URISyntaxException e) {
        log.error("Invalid URI in " + cfgFile + ": " + e.getMessage());
        return Collections.emptyList();
      }
    }
    return configs;
  }
}

@@ -2324,23 +2324,19 @@
 eConfig 
-srcConf
+cfg
  : allCo
@@ -2356,198 +2356,345 @@
     
-RefSpec spec = null;%0A      for (final RefSpec s : srcConf.getPushRefSpecs()) %7B%0A        if (s.matchSource(ref)) %7B%0A          spec = s;%0A          break;%0A        %7D%0A      %7D%0A      if (spec == null
+if (wouldPushRef(cfg, ref)) %7B%0A        for (final URIish uri : cfg.getURIs()) %7B%0A          scheduleImp(project, ref, cfg, expandURI(uri, project));%0A        %7D%0A      %7D%0A    %7D%0A  %7D%0A%0A  private static boolean wouldPushRef(final RemoteConfig cfg, final String ref) %7B%0A    for (final RefSpec s : cfg.getPushRefSpecs()) %7B%0A      if (s.matchSource(ref)
 ) %7B%0A
@@ -2693,38 +2693,41 @@
 ref)) %7B%0A        
-contin
+return tr
 ue;%0A      %7D%0A%0A   
@@ -2726,58 +2726,116 @@
   %7D%0A
-%0A      for (URIish uri : srcConf.getURIs()) %7B%0A    
+    %7D%0A    return false;%0A  %7D%0A%0A  private static URIish expandURI(URIish uri, final Project.NameKey project) %7B%0A
     
@@ -2908,66 +2908,19 @@
     
-    scheduleImp(project, ref, srcConf, uri);%0A      %7D%0A    %7D
+return uri;
 %0A  %7D

Refactor PushQueue.scheduleUpdate to be smaller methods