text:
package org.osc.core.broker.util.crypto;

import static org.osc.core.broker.service.common.VmidcMessages.getString;
import static org.osc.core.broker.service.common.VmidcMessages_.EXCEPTION_KEYPAIR_ZIP_MALFORMED;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.xml.bind.DatatypeConverter;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.openssl.PEMException;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.osc.core.broker.service.api.server.ArchiveApi;
import org.osc.core.broker.service.response.CertificateBasicInfoModel;
import org.osc.core.broker.service.ssl.CertificateResolverModel;
import org.osc.core.broker.service.ssl.TruststoreChangedListener;
import org.osc.core.broker.service.ssl.X509TrustManagerApi;
import org.osc.core.broker.util.ServerUtil;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = X509TrustManagerApi.class, immediate=true)
public final class X509TrustManagerFactory implements X509TrustManager, X509TrustManagerApi {

    private static final String CERTCHAIN_PKI_FILE = "certchain.pkipath";
    private static final String CERTCHAIN_PEM_FILE = "certchain.pem";
    private static final String KEY_PEM_FILE = "key.pem";
    private static final String CERT_CHAIN = "CERTCHAIN";
    private static final String KEY = "KEY";

    private static final Logger LOG = LoggerFactory.getLogger(X509TrustManagerFactory.class);
    private static final String KEYSTORE_TYPE = "JKS";

    // osctrustore stores public certificates needed to establish SSL connection
    // osctrustore also stores private certificate used by application to enable
    // HTTPS - it's also used to establish connection internally
    public static final String TRUSTSTORE_FILE = "data/osctrustore.jks";
    // key entry to properties file that contains password
    // osctrustore stores private certificate used by application to enable HTTPS - it's also used to establish connection internally
    private static final String TRUSTSTORE_PASSWORD_ENTRY_KEY = "truststore.password";
    // alias to truststore password entry in PKC#12 password
    private static final String INTERNAL_ALIAS = "internal";
    private static volatile X509TrustManagerFactory instance = null;
    private static final String ALNUM_FILTER_REGEX = "[^a-zA-Z0-9-_\\.]";

    private X509TrustManager trustManager = null;
    private KeyStore keyStore;
    private CertificateInterceptor listener = null;

    /** Listeners for trust store changes (adding/removing/modifying trust store) */
    private LinkedHashSet<TruststoreChangedListener> truststoreChangedListeners = new LinkedHashSet<>();

    @Reference
    private ArchiveApi archiveApi;

    public static X509TrustManagerFactory getInstance() {
        if (instance == null) {
            // Do this reflectively to avoid pulling in the OSGi API when
            // starting the server.
            ClassLoader loader = X509TrustManagerFactory.class.getClassLoader();
            if(loader != null) {
                Method m = null;
                try {
                    m = loader.getClass().getMethod("getBundle");
                } catch (Exception e) {
                    // ClassLoader is not an OSGi class loader
                    LOG.info("ClassLoader is not an OSGi classloader.", e);
                }
                if(m != null && "org.osgi.framework.Bundle".equals(m.getReturnType().getName())) {
                    throw new IllegalStateException("X509TrustManager component is not yet started");
                } else {
                    instance = new X509TrustManagerFactory();
                }
            }

        }

        return instance;
    }

    public X509TrustManagerFactory() {
        try {
            reloadTrustManager();
        } catch (Exception e) {
            LOG.error("Error occurred during TrustManagerFactory initialization", e);
        }
    }

    @Activate
    void start() {
        setInstance(this);
    }

    private static void setInstance(X509TrustManagerFactory mgr) {
        instance = mgr;
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return this.trustManager.getAcceptedIssuers();
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        this.trustManager.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        try {
            this.trustManager.checkServerTrusted(chain, authType);
        } catch (CertificateException cx) {
            try {
                X509Certificate x509Certificate = chain[0];
                x509Certificate.checkValidity();
                long unixTimestamp = Instant.now().getEpochSecond();
                CertificateResolverModel resolverModel = new CertificateResolverModel(
                        x509Certificate, String.valueOf(unixTimestamp), getSha1Fingerprint(x509Certificate));
                if (this.listener != null) {
                    this.listener.intercept(resolverModel);
                }
            } catch (NoSuchAlgorithmException e) {
                LOG.error("Cannot generate SHA1 fingerprint for certificate", e);
            }
            throw cx;
        }
    }

    /**
     * Set listener for intercepting SSL certificate
     * @param listener - implementation of handling missing SSL certificate
     */
    public void setListener(CertificateInterceptor listener) {
        this.listener = listener;
    }

    /**
     * Unset listener
     */
    public void clearListener() {
        this.listener = null;
    }

    private void reloadTrustManager() throws Exception {
        this.keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
        LOG.debug("Opening trust store file....");
        try (InputStream inputStream = new FileInputStream(TRUSTSTORE_FILE)) {
            this.keyStore.load(inputStream, null); //Password is unnecessary as we are fetching public data
        } catch (FileNotFoundException e) {
            throw new Exception("Failed to load trust store", e);
        } catch (CertificateException e) {
            throw new Exception("Failed to load certificate from trust store", e);
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
        trustManagerFactory.init(this.keyStore);

        for (TrustManager tm : trustManagerFactory.getTrustManagers()) {
            if (tm instanceof X509TrustManager) {
                this.trustManager = (X509TrustManager) tm;
                return;
            }
        }

        throw new NoSuchAlgorithmException("No X509TrustManager in TrustManagerFactory");
    }

    @Override
    public List<CertificateBasicInfoModel> getCertificateInfoList() throws Exception {
        reloadTrustManager();
        ArrayList<CertificateBasicInfoModel> list = new ArrayList<>();
        Enumeration<String> aliases = this.keyStore.aliases();
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            if ("X.509".equals(this.keyStore.getCertificate(alias).getType())) {
                X509Certificate certificate = (X509Certificate) this.keyStore.getCertificate(alias);
                try {
                    CertificateBasicInfoModel infoModel = new CertificateBasicInfoModel(
                            alias, getSha1Fingerprint(certificate), certificate.getIssuerDN().getName(),
                            certificate.getNotBefore(), certificate.getNotAfter(), certificate.getSigAlgName(),
                            certificateToString(certificate));

                    list.add(infoModel);
                } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
                    LOG.error("Failed to add certificate basic info model", e);
                }
            } else {
                list.add(new CertificateBasicInfoModel(alias));
            }
        }
        return list;
    }

    @Override
    public void addEntry(File file) throws Exception {
        String newAlias = cleanFileName(FilenameUtils.removeExtension(file.getName()));
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate certificate;
        try (FileInputStream inputStream = new FileInputStream(file)) {
            certificate = (X509Certificate) cf.generateCertificate(inputStream);
        }
        setOrUpdateCertificate(certificate, newAlias);
    }

    @Override
    public void addEntry(X509Certificate certificate, String newAlias) throws Exception {
        if (fingerprintNotExist(getSha1Fingerprint(certificate))) {
            setOrUpdateCertificate(certificate, newAlias);
        } else {
            throw new Exception("Given certificate fingerprint already exists in trust store");
        }
    }

    private void setOrUpdateCertificate(X509Certificate certificate, String newAlias) throws Exception {

        if (INTERNAL_ALIAS.equals(newAlias)) {
            throw new Exception("Certificate alias " + INTERNAL_ALIAS + " is reserved! Change alias supplied or file name.");
        }

        this.keyStore.setCertificateEntry(newAlias, certificate);
        saveTruststore();
    }

    private void saveTruststore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            Exception, FileNotFoundException {
        try (FileOutputStream outputStream = new FileOutputStream(TRUSTSTORE_FILE)) {
            this.keyStore.store(outputStream, getTruststorePassword());
        }
        reloadTrustManager();
        notifyTruststoreChanged();
    }

    @Override
    public void replaceInternalCertificate(File zipFile, boolean doReboot) throws Exception {
        // Disabling reboot for testing.

        if (zipFile == null) {
            throw new Exception(getString(EXCEPTION_KEYPAIR_ZIP_MALFORMED));
        }

        Map<String, File> files = unzipFilePair(zipFile);
        File pKeyFile = files.get(KEY);
        File chainFile = files.get(CERT_CHAIN);
        // File not needed any more.
        FileUtils.deleteQuietly(zipFile);

        replaceInternalCertificate(pKeyFile, chainFile, doReboot);
    }

    private void replaceInternalCertificate(File pKeyFile, File chainFile, boolean doReboot) throws Exception {
        if (pKeyFile == null || chainFile == null) {
            throw new Exception(getString(EXCEPTION_KEYPAIR_ZIP_MALFORMED));
        }

        LOG.info("Replacing internal private/public keys from files {} and {}.",
                 pKeyFile.getName(), chainFile.getName());

        Certificate[] internalCertificateChain = tryParseCertificateChain(chainFile);
        Key internalKey = tryParsePKCS8PemPrivateKey(pKeyFile);

        try {
            this.keyStore.setKeyEntry(INTERNAL_ALIAS, internalKey, getTruststorePassword(), internalCertificateChain);
            saveTruststore();
        } catch (KeyStoreException e) {
            throw new Exception("Failed to add persist the new internal certificate!", e);
        } finally {
            // Files not needed any more.
            FileUtils.deleteQuietly(pKeyFile);
            FileUtils.deleteQuietly(chainFile);
        }

        if (doReboot) {
            LOG.info("Replaced internal private/public key! Rebooting system ! ! !");
            ServerUtil.execWithLog(new String[] { "/opt/vmidc/bin/vmidc.sh", "--stop"});
        }
    }

    private Key tryParsePKCS8PemPrivateKey(File pKeyFile) throws FileNotFoundException, IOException, PEMException {
        LOG.info("Trying to parse as PKCS8 private key file:" + pKeyFile.getName());

        Key internalKey;
        try (BufferedReader br = new BufferedReader(new FileReader(pKeyFile));
                PEMParser pp = new PEMParser(br);) {
            PrivateKeyInfo pkInfo = (PrivateKeyInfo) pp.readObject();
            internalKey = new JcaPEMKeyConverter().getPrivateKey(pkInfo);
        }

        return internalKey;
    }

    private Certificate[] tryParseCertificateChain(File chainFile) throws Exception {
        Certificate[] internalCertificateChain;

        LOG.info("Trying to parse as X509PEM Path chain file:" + chainFile.getName());
        internalCertificateChain = tryParseX509PEMChain(chainFile);

        if (internalCertificateChain == null || internalCertificateChain.length == 0) {
            LOG.info("Trying to parse as PKI Path chain file:" + chainFile.getName());
            internalCertificateChain = tryParsePKIPathChain(chainFile);
        }

        if (internalCertificateChain == null || internalCertificateChain.length == 0) {
            throw new Exception("Certificate chain file is neither in PKI Path nor X509 PEM formats.");
        }

        return internalCertificateChain;
    }

    private Certificate[] tryParseX509PEMChain(File chainFile) throws Exception {
        Certificate[] internalCertificateChain;
        // Decent initial capacity. Nobody needs more.
        List<Certificate> certChainList = new ArrayList<>(10);

        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(chainFile));
                PEMParser pemParser = new PEMParser(bufferedReader)) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");

            Object holderObj;
            X509CertificateHolder holder;

            try {
                while ((holderObj = pemParser.readObject()) != null) {
                    holder = (X509CertificateHolder) holderObj;
                    Certificate currCert = cf.generateCertificate(new ByteArrayInputStream(holder.getEncoded()));
                    certChainList.add(currCert);
                }
            } catch (Exception e){
                LOG.info("Certificate chain file not in X509+PEM format! : ", e);
                return null;
            }
        }

        internalCertificateChain = certChainList.toArray(new Certificate[0]);
        return internalCertificateChain;
    }

    private Certificate[] tryParsePKIPathChain(File chainFile)
            throws IOException, FileNotFoundException, CertificateException {

        Certificate[] internalCertificateChain = null;
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        try (FileInputStream inputStream = new FileInputStream(chainFile)) {
            CertPath certPath = cf.generateCertPath(inputStream);
            List<? extends Certificate> certList = certPath.getCertificates();
            internalCertificateChain = certList.toArray(new Certificate[]{});
        } catch (CertificateException e){
            LOG.info("Tried and failed to parse file as a PKI :" + chainFile.getName(), e);
        }

        return internalCertificateChain;
    }

    public boolean exists(String alias) throws Exception {
        reloadTrustManager();
        return this.keyStore.containsAlias(alias);
    }

    private boolean fingerprintNotExist(final String fingerprint) throws Exception {
        List<CertificateBasicInfoModel> certificateInfoList = getCertificateInfoList();
        return certificateInfoList.stream().noneMatch(entry -> entry.getSha1Fingerprint().equals(fingerprint));
    }

    public void updateAlias(String oldAlias, String newAlias) throws Exception {
        reloadTrustManager();
        if (this.keyStore.containsAlias(oldAlias)) {
            X509Certificate certificate = (X509Certificate) this.keyStore.getCertificate(oldAlias);
            removeEntry(oldAlias);
            addEntry(certificate, newAlias);
            try(FileOutputStream outputStream = new FileOutputStream(TRUSTSTORE_FILE)) {
                this.keyStore.store(outputStream, getTruststorePassword());
            }
        }
        notifyTruststoreChanged();
    }

    public void removeEntry(String alias) throws Exception {
        reloadTrustManager();
        this.keyStore.deleteEntry(alias);
        saveTruststore();
    }

    /**
     * Clean file name from unwanted chars - only alphabetical characters and digits are allowed
     *
     * @param filename input file name to clean
     * @return cleaned filename
     */
    private String cleanFileName(String filename) {
        return filename.replaceAll(X509TrustManagerFactory.ALNUM_FILTER_REGEX, "");
    }

    @Override
    public String getSha1Fingerprint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException, IllegalArgumentException {

        if(cert == null) {
            throw new IllegalArgumentException("Provided certificate is empty");
        }

        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] der = cert.getEncoded();
        md.update(der);
        byte[] digest = md.digest();
        return DatatypeConverter.printHexBinary(digest);
    }

    /**
     * Interface which allows to intercept missing certificates. Should be defined before calling REST API
     */
    public interface CertificateInterceptor {
        void intercept(CertificateResolverModel model);
    }

    private char[] getTruststorePassword() throws Exception {
        // password to keystore to retrieve truststore manager password
        return getSecurityProperty(TRUSTSTORE_PASSWORD_ENTRY_KEY).toCharArray();
    }

    private String getSecurityProperty(String entryKey) throws Exception {
        // output property
        String property;

        Properties properties = new Properties();
        try {
            properties.load(getClass().getResourceAsStream(EncryptionUtil.SECURITY_PROPS_RESOURCE_PATH));
        } catch (IOException e) {
            throw new Exception("Failed to load entry from security properties.", e);
        }
        property = properties.getProperty(entryKey);

        if (property == null || property.isEmpty()) {
            throw new Exception("No entry defined in properties.");
        }

        return property;
    }

    private void notifyTruststoreChanged() {
        List<TruststoreChangedListener> toCall;
        synchronized (this) {
            toCall = new ArrayList<>(this.truststoreChangedListeners);
        }
        toCall.stream().forEach(listener -> listener.truststoreChanged());
    }

    @Reference(policy=ReferencePolicy.DYNAMIC, cardinality=ReferenceCardinality.MULTIPLE)
    synchronized void addTruststoreChangedListener(TruststoreChangedListener listener) {
        this.truststoreChangedListeners.add(listener);
    }

    synchronized void removeTruststoreChangedListener(TruststoreChangedListener listener) {
        this.truststoreChangedListeners.remove(listener);
    }

    @Override
    public String certificateToString(X509Certificate certificate) {
        StringWriter sw = new StringWriter();
        try {
            sw.write("-----BEGIN CERTIFICATE-----\n");
            sw.write(DatatypeConverter.printBase64Binary(certificate.getEncoded()).replaceAll("(.{64})", "$1\n"));
            sw.write("\n-----END CERTIFICATE-----");
        } catch (CertificateEncodingException e) {
            LOG.error("Cannot encode certificate", e);
        }
        return sw.toString();
    }

    private Map<String, File> unzipFilePair(File zipFile) throws Exception {

        String unzipFolder = zipFile.getParent() != null ? zipFile.getParent() : ".";

        this.archiveApi.unzip(zipFile.getAbsolutePath(), unzipFolder);

        File privateKeyFile = new File(unzipFolder, KEY_PEM_FILE);
        if (!privateKeyFile.exists()) {
            throw new Exception(getString(EXCEPTION_KEYPAIR_ZIP_MALFORMED));
        }

        File certChainFile = new File(unzipFolder, CERTCHAIN_PEM_FILE);
        if (!certChainFile.exists()) {
            certChainFile = new File(unzipFolder, CERTCHAIN_PKI_FILE);
        }
        if (!certChainFile.exists()) {
            throw new Exception(getString(EXCEPTION_KEYPAIR_ZIP_MALFORMED));
        }

        Map<String, File> retVal = new HashMap<>();
        retVal.put(KEY, privateKeyFile);
        retVal.put(CERT_CHAIN, certChainFile);
        return retVal;
    }
}


text:
from mock import call, create_autospec, patch

from apache.aurora.client.api.scheduler_client import SchedulerClient
from apache.aurora.client.cli import EXIT_UNKNOWN_ERROR
from apache.aurora.client.cli.client import AuroraCommandLine

from .util import AuroraClientCommandTest

from gen.apache.aurora.api import AuroraAdmin
from gen.apache.aurora.api.ttypes import (
    JobKey,
    ResponseCode,
    Result,
    ScheduleStatusResult,
    TaskQuery
)

class TestApiFromCLI(AuroraClientCommandTest):
  """A container for tests that are probing at API functionality,
  to see if the CLI can handle API-level errors.
  """

  @classmethod
  def create_mock_scheduled_task_no_metadata(cls):
    result = cls.create_mock_scheduled_tasks()
    for task in result:
      task.assignedTask.task.metadata = None
    return result

  @classmethod
  def create_status_response(cls):
    resp = cls.create_simple_success_response()
    resp.result = Result(
        scheduleStatusResult=ScheduleStatusResult(tasks=set(cls.create_scheduled_tasks())))
    return resp

  @classmethod
  def create_status_response_null_metadata(cls):
    resp = cls.create_simple_success_response()
    resp.result = Result(
        scheduleStatusResult=ScheduleStatusResult(
            tasks=set(cls.create_mock_scheduled_task_no_metadata())))
    return resp

  @classmethod
  def create_failed_status_response(cls):
    return cls.create_blank_response(ResponseCode.INVALID_REQUEST, 'No tasks found for query')

  def test_successful_status_deep(self):
    """Test the status command more deeply: in a request with a fully specified
    job, it should end up doing a query using getTasksWithoutConfigs."""
    mock_scheduler_client = create_autospec(spec=SchedulerClient, instance=True)
    mock_thrift_client = create_autospec(spec=AuroraAdmin.Client, instance=True)
    mock_scheduler_client.get_thrift_client.return_value = mock_thrift_client
    mock_thrift_client.getTasksWithoutConfigs.return_value = self.create_status_response()
    with patch('apache.aurora.client.api.scheduler_client.SchedulerClient.get',
               return_value=mock_scheduler_client):
      cmd = AuroraCommandLine()
      cmd.execute(['job', 'status', 'west/bozo/test/hello'])
      assert mock_thrift_client.getTasksWithoutConfigs.mock_calls == [
          call(TaskQuery(jobKeys=[JobKey(role='bozo', environment='test', name='hello')]))]

  def test_status_api_failure(self):
    mock_scheduler_client = create_autospec(spec=SchedulerClient, instance=True)
    mock_thrift_client = create_autospec(spec=AuroraAdmin.Client, instance=True)
    mock_scheduler_client.get_thrift_client.return_value = mock_thrift_client

    mock_thrift_client.getTasksWithoutConfigs.side_effect = IOError("Uh-Oh")
    with patch('apache.aurora.client.api.scheduler_client.SchedulerClient.get',
            return_value=mock_scheduler_client):
      cmd = AuroraCommandLine()
      # This should create a scheduler client, set everything up, and then issue a
      # getTasksWithoutConfigs call against the mock_scheduler_client. That should raise an
      # exception, which results in the command failing with an error code.
      result = cmd.execute(['job', 'status', 'west/bozo/test/hello'])
      assert result == EXIT_UNKNOWN_ERROR
      assert mock_thrift_client.getTasksWithoutConfigs.mock_calls == [
        call(TaskQuery(jobKeys=[JobKey(role='bozo', environment='test', name='hello')]))]

redpajama_set_name: RedPajamaGithub


text:
<?php

if (!defined('SMF'))
	die('Hacking attempt...');

/*
	void sportal_admin_shoutbox_main()
		// !!!

	void sportal_admin_shoutbox_list()
		// !!!

	void sportal_admin_shoutbox_edit()
		// !!!

	void sportal_admin_shoutbox_delete()
		// !!!

	void sportal_admin_shoutbox_status()
		// !!!
*/

function sportal_admin_shoutbox_main()
{
	global $context, $txt, $scripturl, $sourcedir;

	if (!allowedTo('sp_admin'))
		isAllowedTo('sp_manage_shoutbox');

	loadTemplate('PortalAdminShoutbox');

	$subActions = array(
		'list' => 'sportal_admin_shoutbox_list',
		'add' => 'sportal_admin_shoutbox_edit',
		'edit' => 'sportal_admin_shoutbox_edit',
		'prune' => 'sportal_admin_shoutbox_prune',
		'delete' => 'sportal_admin_shoutbox_delete',
		'status' => 'sportal_admin_shoutbox_status',
		'blockredirect' => 'sportal_admin_shoutbox_block_redirect',
	);

	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'list';

	$context['sub_action'] = $_REQUEST['sa'];

	$context['admin_tabs'] = array(
		'title' => $txt['sp_admin_shoutbox_title'],
		'help' => 'sp_ShoutboxArea',
		'description' => $txt['sp_admin_shoutbox_desc'],
		'tabs' => array(
			'list' => array(
				'title' => $txt['sp_admin_shoutbox_list'],
				'description' => $txt['sp_admin_shoutbox_desc'],
				'href' => $scripturl . '?action=manageportal;area=portalshoutbox;sa=list',
				'is_selected' => $_REQUEST['sa'] == 'list' || $_REQUEST['sa'] == 'edit',
			),
			'add' => array(
				'title' => $txt['sp_admin_shoutbox_add'],
				'description' => $txt['sp_admin_shoutbox_desc'],
				'href' => $scripturl . '?action=manageportal;area=portalshoutbox;sa=add',
				'is_selected' => $_REQUEST['sa'] == 'add',
			),
		),
	);

	$subActions[$_REQUEST['sa']]();
}

function sportal_admin_shoutbox_list()
{
	global $txt, $db_prefix, $context, $scripturl;

	if (!empty($_POST['remove_shoutbox']) && !empty($_POST['remove']) && is_array($_POST['remove']))
	{
		checkSession();

		foreach ($_POST['remove'] as $index => $page_id)
			$_POST['remove'][(int) $index] = (int) $page_id;

		db_query("
			DELETE FROM {$db_prefix}sp_shoutboxes
			WHERE ID_SHOUTBOX IN (" . implode(', ', $_POST['remove']) . ")", __FILE__, __LINE__);

		db_query("
			DELETE FROM {$db_prefix}sp_shouts
			WHERE ID_SHOUTBOX IN (" . implode(', ', $_POST['remove']) . ")", __FILE__, __LINE__);
	}

	$sort_methods = array(
		'name' =>  array(
			'down' => 'name ASC',
			'up' => 'name DESC'
		),
		'num_shouts' => array(
			'down' => 'num_shouts ASC',
			'up' => 'num_shouts DESC'
		),
		'caching' => array(
			'down' => 'caching ASC',
			'up' => 'caching DESC'
		),
		'status' => array(
			'down' => 'status ASC',
			'up' => 'status DESC'
		),
	);

	$context['columns'] = array(
		'name' => array(
			'width' => '40%',
			'label' => $txt['sp_admin_shoutbox_col_name'],
			'sortable' => true
		),
		'num_shouts' => array(
			'width' => '15%',
			'label' => $txt['sp_admin_shoutbox_col_shouts'],
			'sortable' => true
		),
		'caching' => array(
			'width' => '15%',
			'label' => $txt['sp_admin_shoutbox_col_caching'],
			'sortable' => true
		),
		'status' => array(
			'width' => '15%',
			'label' => $txt['sp_admin_shoutbox_col_status'],
			'sortable' => true
		),
		'actions' => array(
			'width' => '15%',
			'label' => $txt['sp_admin_shoutbox_col_actions'],
			'sortable' => false
		),
	);

	if (!isset($_REQUEST['sort']) || !isset($sort_methods[$_REQUEST['sort']]))
		$_REQUEST['sort'] = 'name';

	foreach ($context['columns'] as $col => $dummy)
	{
		$context['columns'][$col]['selected'] = $col == $_REQUEST['sort'];
		$context['columns'][$col]['href'] = $scripturl . '?action=manageportal;area=portalshoutbox;sa=list;sort=' . $col;

		if (!isset($_REQUEST['desc']) && $col == $_REQUEST['sort'])
			$context['columns'][$col]['href'] .= ';desc';

		$context['columns'][$col]['link'] = '' . $context['columns'][$col]['label'] . '';
	}

	$context['sort_by'] = $_REQUEST['sort'];
	$context['sort_direction'] = !isset($_REQUEST['desc']) ? 'down' : 'up';

	$request = db_query("
		SELECT COUNT(*)
		FROM {$db_prefix}sp_shoutboxes", __FILE__, __LINE__);
	list ($total_shoutbox) =  mysql_fetch_row($request);
	mysql_free_result($request);

	$context['page_index'] = constructPageIndex($scripturl . '?action=manageportal;area=portalshoutbox;sa=list;sort=' . $_REQUEST['sort'] . (isset($_REQUEST['desc']) ? ';desc' : ''), $_REQUEST['start'], $total_shoutbox, 20);
	$context['start'] = $_REQUEST['start'];

	$request = db_query("
		SELECT ID_SHOUTBOX, name, caching, status, num_shouts
		FROM {$db_prefix}sp_shoutboxes
		ORDER BY " . $sort_methods[$_REQUEST['sort']][$context['sort_direction']] . "
		LIMIT $context[start], 20", __FILE__, __LINE__);
	$context['shoutboxes'] = array();
	while ($row = mysql_fetch_assoc($request))
	{
		$context['shoutboxes'][$row['ID_SHOUTBOX']] = array(
			'id' => $row['ID_SHOUTBOX'],
			'name' => $row['name'],
			'shouts' => $row['num_shouts'],
			'caching' => $row['caching'],
			'status' => $row['status'],
			'status_image' => '' . sp_embed_image(empty($row['status']) ? 'deactive' : 'active', $txt['sp_admin_shoutbox_' . (empty($row['status']) ? 'de' : '') . 'activate']) . '',
			'actions' => array(
				'edit' => '' . sp_embed_image('modify') . '',
				'prune' => '' . sp_embed_image('bin') . '',
				'delete' => '' . sp_embed_image('delete') . '',
			)
		);
	}
	mysql_free_result($request);

	$context['sub_template'] = 'shoutbox_list';
	$context['page_title'] = $txt['sp_admin_shoutbox_list'];
}

function sportal_admin_shoutbox_edit()
{
	global $txt, $context, $modSettings, $db_prefix, $func;

	$context['SPortal']['is_new'] = empty($_REQUEST['shoutbox_id']);

	if (!empty($_POST['submit']))
	{
		checkSession();

		if (!isset($_POST['name']) || $func['htmltrim'](addslashes($func['htmlspecialchars'](stripslashes($_POST['name']), ENT_QUOTES))) === '')
			fatal_lang_error('sp_error_shoutbox_name_empty', false);

		$request = db_query("
			SELECT ID_SHOUTBOX
			FROM {$db_prefix}sp_shoutboxes
			WHERE name = '" . addslashes($func['htmlspecialchars'](stripslashes($_POST['name']), ENT_QUOTES)) . "'
				AND ID_SHOUTBOX != " . (int) $_POST['shoutbox_id'] . "
			LIMIT 1", __FILE__, __LINE__);
		list ($has_duplicate) = mysql_fetch_row($request);
		mysql_free_result($request);

		if (!empty($has_duplicate))
			fatal_lang_error('sp_error_shoutbox_name_duplicate', false);

		$permission_set = 0;
		$groups_allowed = $groups_denied = '';

		if (!empty($_POST['permission_set']))
			$permission_set = (int) $_POST['permission_set'];
		elseif (!empty($_POST['membergroups']) && is_array($_POST['membergroups']))
		{
			$groups_allowed = $groups_denied = array();

			foreach ($_POST['membergroups'] as $id => $value)
			{
				if ($value == 1)
					$groups_allowed[] = (int) $id;
				elseif ($value == -1)
					$groups_denied[] = (int) $id;
			}

			$groups_allowed = implode(',', $groups_allowed);
			$groups_denied = implode(',', $groups_denied);
		}

		if (isset($_POST['moderator_groups']) && is_array($_POST['moderator_groups']) && count($_POST['moderator_groups']) > 0)
		{
			foreach ($_POST['moderator_groups'] as $id => $group)
				$_POST['moderator_groups'][$id] = (int) $group;

			$_POST['moderator_groups'] = implode(',', $_POST['moderator_groups']);
		}
		else
			$_POST['moderator_groups'] = '';

		if (!empty($_POST['allowed_bbc']) && is_array($_POST['allowed_bbc']))
		{
			foreach ($_POST['allowed_bbc'] as $id => $tag)
				$_POST['allowed_bbc'][$id] = addslashes($func['htmlspecialchars'](stripslashes($tag), ENT_QUOTES));

			$_POST['allowed_bbc'] = implode(',', $_POST['allowed_bbc']);
		}
		else
			$_POST['allowed_bbc'] = '';

		$fields = array(
			'name' => 'string',
			'permission_set' => 'int',
			'groups_allowed' => 'string',
			'groups_denied' => 'string',
			'moderator_groups' => 'string',
			'warning' => 'string',
			'allowed_bbc' => 'string',
			'height' => 'int',
			'num_show' => 'int',
			'num_max' => 'int',
			'reverse' => 'int',
			'caching' => 'int',
			'refresh' => 'int',
			'status' => 'int',
		);

		$shoutbox_info = array(
			'id' => (int) $_POST['shoutbox_id'],
			'name' => addslashes($func['htmlspecialchars'](stripslashes($_POST['name']), ENT_QUOTES)),
			'permission_set' => $permission_set,
			'groups_allowed' => $groups_allowed,
			'groups_denied' => $groups_denied,
			'moderator_groups' => $_POST['moderator_groups'],
			'warning' => addslashes($func['htmlspecialchars'](stripslashes($_POST['warning']), ENT_QUOTES)),
			'allowed_bbc' => $_POST['allowed_bbc'],
			'height' => (int) $_POST['height'],
			'num_show' => (int) $_POST['num_show'],
			'num_max' => (int) $_POST['num_max'],
			'reverse' => !empty($_POST['reverse']) ? 1 : 0,
			'caching' => !empty($_POST['caching']) ? 1 : 0,
			'refresh' => (int) $_POST['refresh'],
			'status' => !empty($_POST['status']) ? 1 : 0,
		);

		if ($context['SPortal']['is_new'])
		{
			unset($shoutbox_info['id']);

			$insert = array();
			foreach ($shoutbox_info as $key => $info)
				$insert[$key] = "'" . $info . "'";

			db_query("
				INSERT INTO {$db_prefix}sp_shoutboxes
					(" . implode(', ', array_keys($insert)) . ")
				VALUES
					(" . implode(', ', $insert) . ")", __FILE__, __LINE__);
			$shoutbox_info['id'] = db_insert_id();
		}
		else
		{
			$update_fields = array();
			foreach ($fields as $name => $type)
				$update_fields[] = $name . ' = \'' . $shoutbox_info[$name] . '\'';

			db_query("
				UPDATE {$db_prefix}sp_shoutboxes
				SET " . implode(', ', $update_fields) . "
				WHERE ID_SHOUTBOX = $shoutbox_info[id]
				LIMIT 1", __FILE__, __LINE__);
		}

		sportal_update_shoutbox($shoutbox_info['id']);

		if ($context['SPortal']['is_new'] && (allowedTo(array('sp_admin', 'sp_manage_blocks'))))
			redirectexit('action=manageportal;area=portalshoutbox;sa=blockredirect;shoutbox=' . $shoutbox_info['id']);
		else
			redirectexit('action=manageportal;area=portalshoutbox');
	}

	if ($context['SPortal']['is_new'])
	{
		$context['SPortal']['shoutbox'] = array(
			'id' => 0,
			'name' => $txt['sp_shoutbox_default_name'],
			'permission_set' => 3,
			'groups_allowed' => array(),
			'groups_denied' => array(),
			'moderator_groups' => array(),
			'warning' => '',
			'allowed_bbc' => array('b', 'i', 'u', 's', 'url', 'code', 'quote', 'me'),
			'height' => 200,
			'num_show' => 20,
			'num_max' => 1000,
			'reverse' => 0,
			'caching' => 1,
			'refresh' => 0,
			'status' => 1,
		);
	}
	else
	{
		$_REQUEST['shoutbox_id'] = (int) $_REQUEST['shoutbox_id'];
		$context['SPortal']['shoutbox'] = sportal_get_shoutbox($_REQUEST['shoutbox_id']);
	}

	loadLanguage('Post');

	$context['SPortal']['shoutbox']['groups'] = sp_load_membergroups();
	sp_loadMemberGroups($context['SPortal']['shoutbox']['moderator_groups'], 'moderator', 'moderator_groups');

	$context['allowed_bbc'] = array(
		'b' => $txt[253],
		'i' => $txt[254],
		'u' => $txt[255],
		's' => $txt[441],
		'pre' => $txt[444],
		'flash' => $txt[433],
		'img' => $txt[435],
		'url' => $txt[257],
		'email' => $txt[258],
		'ftp' => $txt[434],
		'glow' => $txt[442],
		'shadow' => $txt[443],
		'sup' => $txt[447],
		'sub' => $txt[448],
		'tt' => $txt[440],
		'code' => $txt[259],
		'quote' => $txt[260],
		'size' => $txt[532],
		'font' => $txt[533],
		'color' => $txt['change_color'],
		'me' => 'me',
	);

	$disabled_tags = array();
	if (!empty($modSettings['disabledBBC']))
		$disabled_tags = explode(',', $modSettings['disabledBBC']);
	if (empty($modSettings['enableEmbeddedFlash']))
		$disabled_tags[] = 'flash';

	foreach ($disabled_tags as $tag)
	{
		if ($tag == 'list')
			$context['disabled_tags']['orderlist'] = true;

		$context['disabled_tags'][trim($tag)] = true;
	}

	$context['page_title'] = $context['SPortal']['is_new'] ? $txt['sp_admin_shoutbox_add'] : $txt['sp_admin_shoutbox_edit'];
	$context['sub_template'] = 'shoutbox_edit';
}

function sportal_admin_shoutbox_prune()
{
	global $db_prefix, $func, $context, $txt;

	$shoutbox_id = empty($_REQUEST['shoutbox_id']) ? 0 : (int) $_REQUEST['shoutbox_id'];
	$context['shoutbox'] = sportal_get_shoutbox($shoutbox_id);

	if (empty($context['shoutbox']))
		fatal_lang_error('error_sp_shoutbox_not_exist', false);

	if (!empty($_POST['submit']))
	{
		checkSession();

		if (!empty($_POST['type']))
		{
			$query = array('ID_SHOUTBOX = ' . $shoutbox_id);

			if ($_POST['type'] == 'days' && !empty($_POST['days']))
				$query[] = 'log_time < ' . (time() - $_POST['days'] * 86400);
			elseif ($_POST['type'] == 'member' && !empty($_POST['member']))
			{
				$member_name = strtr(trim($func['htmlspecialchars']($_POST['member'], ENT_QUOTES)), array('\'' => '''));
				$request = db_query("
					SELECT ID_MEMBER
					FROM {$db_prefix}members
					WHERE memberName = '$member_name'
						OR realName = '$member_name'
					LIMIT 1", __FILE__, __LINE__);
				list ($member_id) =  mysql_fetch_row($request);
				mysql_free_result($request);

				if (!empty($member_id))
					$query[] = 'id_member = ' . $member_id;
			}

			if ($_POST['type'] == 'all' || count($query) > 1)
			{
				db_query("
					DELETE FROM {$db_prefix}sp_shouts
					WHERE " . implode(" AND ", $query), __FILE__, __LINE__);

				if ($_POST['type'] != 'all')
				{
					$request = db_query("
						SELECT COUNT(*)
						FROM {$db_prefix}sp_shouts
						WHERE ID_SHOUTBOX = $shoutbox_id
						LIMIT 1", __FILE__, __LINE__);
					list ($total_shouts) =  mysql_fetch_row($request);
					mysql_free_result($request);
				}
				else
					$total_shouts = 0;

				db_query("
					UPDATE {$db_prefix}sp_shoutboxes
					SET num_shouts = $total_shouts
					WHERE ID_SHOUTBOX = $shoutbox_id", __FILE__, __LINE__);

				sportal_update_shoutbox($shoutbox_id);
			}
		}

		redirectexit('action=manageportal;area=portalshoutbox');
	}

	$context['page_title'] = $txt['sp_admin_shoutbox_prune'];
	$context['sub_template'] = 'shoutbox_prune';
}

function sportal_admin_shoutbox_delete()
{
	global $db_prefix;

	checkSession('get');

	$shoutbox_id = !empty($_REQUEST['shoutbox_id']) ? (int) $_REQUEST['shoutbox_id'] : 0;

	db_query("
		DELETE FROM {$db_prefix}sp_shoutboxes
		WHERE ID_SHOUTBOX = $shoutbox_id", __FILE__, __LINE__);

	db_query("
		DELETE FROM {$db_prefix}sp_shouts
		WHERE ID_SHOUTBOX = $shoutbox_id", __FILE__, __LINE__);

	redirectexit('action=manageportal;area=portalshoutbox');
}

function sportal_admin_shoutbox_status()
{
	global $db_prefix;

	checkSession('get');

	$shoutbox_id = !empty($_REQUEST['shoutbox_id']) ? (int) $_REQUEST['shoutbox_id'] : 0;

	db_query("
		UPDATE {$db_prefix}sp_shoutboxes
		SET status = CASE WHEN status = 1 THEN 0 ELSE 1 END
		WHERE ID_SHOUTBOX = $shoutbox_id
		LIMIT 1", __FILE__, __LINE__);

	redirectexit('action=manageportal;area=portalshoutbox');
}

function sportal_admin_shoutbox_block_redirect()
{
	global $context, $scripturl, $txt;

	if (!allowedTo('sp_admin'))
		isAllowedTo('sp_manage_blocks');

	$context['page_title'] = $txt['sp_admin_shoutbox_add'];
	$context['redirect_message'] = sprintf($txt['sp_admin_shoutbox_block_redirect_message'], $scripturl . '?action=manageportal;area=portalblocks;sa=add;selected_type=sp_shoutbox;parameters[]=shoutbox;shoutbox=' . $_GET['shoutbox'], $scripturl . '?action=manageportal;area=portalshoutbox');
	$context['sub_template'] = 'shoutbox_block_redirect';
}

?>


text:
package com.ryantenney.metrics.spring.reporter;

import com.codahale.metrics.JmxReporter;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;

import javax.management.MBeanServer;
import java.util.concurrent.TimeUnit;

public class JmxReporterFactoryBean extends AbstractReporterFactoryBean<JmxReporter> implements SmartLifecycle, DisposableBean {

	// Optional
	public static final String DOMAIN = "domain";
	public static final String DURATION_UNIT = "duration-unit";
	public static final String RATE_UNIT = "rate-unit";
	public static final String MBEAN_SERVER_REF = "mbean-server-ref";

	private boolean running = false;

	@Override
	public Class<JmxReporter> getObjectType() {
		return JmxReporter.class;
	}

	@Override
	protected JmxReporter createInstance() {
		final JmxReporter.Builder reporter = JmxReporter.forRegistry(getMetricRegistry());

		if (hasProperty(DOMAIN)) {
			reporter.inDomain(getProperty(DOMAIN));
		}

		if (hasProperty(DURATION_UNIT)) {
			reporter.convertDurationsTo(getProperty(DURATION_UNIT, TimeUnit.class));
		}

		if (hasProperty(RATE_UNIT)) {
			reporter.convertRatesTo(getProperty(RATE_UNIT, TimeUnit.class));
		}

		reporter.filter(getMetricFilter());

		if (hasProperty(MBEAN_SERVER_REF)) {
			reporter.registerWith(getPropertyRef(MBEAN_SERVER_REF, MBeanServer.class));
		}

		return reporter.build();
	}

	@Override
	public void start() {
		if (isEnabled() && !isRunning()) {
			getObject().start();
			running = true;
		}
	}

	@Override
	public void stop() {
		if (isRunning()) {
			getObject().stop();
			running = false;
		}
	}

	@Override
	public boolean isRunning() {
		return running;
	}

	@Override
	public void destroy() throws Exception {
		stop();
	}

	@Override
	public boolean isAutoStartup() {
		return true;
	}

	@Override
	public void stop(Runnable runnable) {
		stop();
		runnable.run();
	}

	@Override
	public int getPhase() {
		return 0;
	}

}


text:
#ifndef TENSORFLOW_LITE_SHARED_LIBRARY_H_
#define TENSORFLOW_LITE_SHARED_LIBRARY_H_

#if defined(_WIN32)
// Windows does not have dlfcn.h/dlsym, use GetProcAddress() instead.
#include <windows.h>
#else
#include <dlfcn.h>
#endif  // defined(_WIN32)

namespace tflite {

// SharedLibrary provides a uniform set of APIs across different platforms to
// handle dynamic library operations
class SharedLibrary {
 public:
#if defined(_WIN32)
  static inline void* LoadLibrary(const char* lib) {
    return ::LoadLibrary(lib);
  }
  static inline void* GetLibrarySymbol(void* handle, const char* symbol) {
    return reinterpret_cast<void*>(
        GetProcAddress(static_cast<HMODULE>(handle), symbol));
  }
  static inline void* GetSymbol(const char* symbol) {
    return reinterpret_cast<void*>(GetProcAddress(nullptr, symbol));
  }
  static inline int UnLoadLibrary(void* handle) {
    return FreeLibrary(static_cast<HMODULE>(handle));
  }
  static inline const char* GetError() { return "Unknown"; }
#else
  static inline void* LoadLibrary(const char* lib) {
    return dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
  }
  static inline void* GetLibrarySymbol(void* handle, const char* symbol) {
    return dlsym(handle, symbol);
  }
  static inline void* GetSymbol(const char* symbol) {
    return dlsym(RTLD_DEFAULT, symbol);
  }
  static inline int UnLoadLibrary(void* handle) { return dlclose(handle); }
  static inline const char* GetError() { return dlerror(); }
#endif  // defined(_WIN32)
};

}  // namespace tflite

#endif  // TENSORFLOW_LITE_SHARED_LIBRARY_H_


text:
package io.crate.operation;

import com.google.common.util.concurrent.SettableFuture;
import io.crate.core.collections.CollectionBucket;
import io.crate.core.collections.Row;
import io.crate.executor.QueryResult;
import io.crate.executor.TaskResult;
import io.crate.jobs.ExecutionState;
import io.crate.operation.projectors.Projector;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * RowDownstream that will set a TaskResultFuture once the result is ready.
 * It will also close the associated context once it is done
 */
public class QueryResultRowDownstream implements Projector {

    private final List<SettableFuture<TaskResult>> results;
    private final AtomicInteger registeredUpstreams = new AtomicInteger(0);

    public QueryResultRowDownstream(List<SettableFuture<TaskResult>> results) {
        this.results = results;
    }

    @Override
    public RowDownstreamHandle registerUpstream(RowUpstream upstream) {
        int upstreamId = registeredUpstreams.getAndIncrement();
        final SettableFuture<TaskResult> result = results.get(upstreamId);
        return new RowDownstreamHandle() {

            List<Object[]> rows = new ArrayList<>();

            @Override
            public boolean setNextRow(Row row) {
                rows.add(row.materialize());
                return true;
            }

            @Override
            public void finish() {
                result.set(new QueryResult(new CollectionBucket(rows)));
            }

            @Override
            public void fail(Throwable throwable) {
                result.setException(throwable);
            }
        };
    }

    @Override
    public void startProjection(ExecutionState executionState) {

    }

    @Override
    public void downstream(RowDownstream downstream) {
        throw new UnsupportedOperationException("QueryResultRowDownstream can't have another downstream");
    }

    @Override
    public void pause() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void resume(boolean async) {
        throw new UnsupportedOperationException();
    }
}


text:
// Copyright (C) 2012 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.restapi.change;

import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static java.util.stream.Collectors.joining;

import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.SubmitTypeRecord;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.submit.ChangeSet;
import com.google.gerrit.server.submit.MergeOp;
import com.google.gerrit.server.submit.MergeSuperSet;
import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;

@Singleton
public class Submit
    implements RestModifyView<RevisionResource, SubmitInput>, UiAction<RevisionResource> {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private static final String DEFAULT_TOOLTIP = "Submit patch set ${patchSet} into ${branch}";
  private static final String DEFAULT_TOOLTIP_ANCESTORS =
      "Submit patch set ${patchSet} and ancestors (${submitSize} changes "
          + "altogether) into ${branch}";
  private static final String DEFAULT_TOPIC_TOOLTIP =
      "Submit all ${topicSize} changes of the same topic "
          + "(${submitSize} changes including ancestors and other "
          + "changes related by topic)";
  private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
      "This change depends on other hidden changes which are not ready";
  private static final String CLICK_FAILURE_TOOLTIP = "Clicking the button would fail";
  private static final String CHANGE_UNMERGEABLE = "Problems with integrating this change";

  private final GitRepositoryManager repoManager;
  private final PermissionBackend permissionBackend;
  private final Provider<MergeOp> mergeOpProvider;
  private final Provider<MergeSuperSet> mergeSuperSet;
  private final AccountResolver accountResolver;
  private final String label;
  private final String labelWithParents;
  private final ParameterizedString titlePattern;
  private final ParameterizedString titlePatternWithAncestors;
  private final String submitTopicLabel;
  private final ParameterizedString submitTopicTooltip;
  private final boolean submitWholeTopic;
  private final Provider<InternalChangeQuery> queryProvider;
  private final PatchSetUtil psUtil;
  private final ProjectCache projectCache;
  private final ChangeJson.Factory json;

  @Inject
  Submit(
      GitRepositoryManager repoManager,
      PermissionBackend permissionBackend,
      Provider<MergeOp> mergeOpProvider,
      Provider<MergeSuperSet> mergeSuperSet,
      AccountResolver accountResolver,
      @GerritServerConfig Config cfg,
      Provider<InternalChangeQuery> queryProvider,
      PatchSetUtil psUtil,
      ProjectCache projectCache,
      ChangeJson.Factory json) {
    this.repoManager = repoManager;
    this.permissionBackend = permissionBackend;
    this.mergeOpProvider = mergeOpProvider;
    this.mergeSuperSet = mergeSuperSet;
    this.accountResolver = accountResolver;
    this.label =
        MoreObjects.firstNonNull(
            Strings.emptyToNull(cfg.getString("change", null, "submitLabel")), "Submit");
    this.labelWithParents =
        MoreObjects.firstNonNull(
            Strings.emptyToNull(cfg.getString("change", null, "submitLabelWithParents")),
            "Submit including parents");
    this.titlePattern =
        new ParameterizedString(
            MoreObjects.firstNonNull(
                cfg.getString("change", null, "submitTooltip"), DEFAULT_TOOLTIP));
    this.titlePatternWithAncestors =
        new ParameterizedString(
            MoreObjects.firstNonNull(
                cfg.getString("change", null, "submitTooltipAncestors"),
                DEFAULT_TOOLTIP_ANCESTORS));
    submitWholeTopic = MergeSuperSet.wholeTopicEnabled(cfg);
    this.submitTopicLabel =
        MoreObjects.firstNonNull(
            Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
            "Submit whole topic");
    this.submitTopicTooltip =
        new ParameterizedString(
            MoreObjects.firstNonNull(
                cfg.getString("change", null, "submitTopicTooltip"), DEFAULT_TOPIC_TOOLTIP));
    this.queryProvider = queryProvider;
    this.psUtil = psUtil;
    this.projectCache = projectCache;
    this.json = json;
  }

  @Override
  public Response<ChangeInfo> apply(RevisionResource rsrc, @Nullable SubmitInput input)
      throws RestApiException, RepositoryNotFoundException, IOException, PermissionBackendException,
          UpdateException, ConfigInvalidException {
    if (input == null) {
      input = new SubmitInput();
    }
    input.onBehalfOf = Strings.emptyToNull(input.onBehalfOf);
    IdentifiedUser submitter;
    if (input.onBehalfOf != null) {
      submitter = onBehalfOf(rsrc, input);
    } else {
      rsrc.permissions().check(ChangePermission.SUBMIT);
      submitter = rsrc.getUser().asIdentifiedUser();
    }
    projectCache
        .get(rsrc.getProject())
        .orElseThrow(illegalState(rsrc.getProject()))
        .checkStatePermitsWrite();

    return Response.ok(json.noOptions().format(mergeChange(rsrc, submitter, input)));
  }

  @UsedAt(UsedAt.Project.GOOGLE)
  public Change mergeChange(RevisionResource rsrc, IdentifiedUser submitter, SubmitInput input)
      throws RestApiException, IOException, UpdateException, ConfigInvalidException,
          PermissionBackendException {
    Change change = rsrc.getChange();
    if (!change.isNew()) {
      throw new ResourceConflictException("change is " + ChangeUtil.status(change));
    } else if (!ProjectUtil.branchExists(repoManager, change.getDest())) {
      throw new ResourceConflictException(
          String.format("destination branch \"%s\" not found.", change.getDest().branch()));
    } else if (!rsrc.getPatchSet().id().equals(change.currentPatchSetId())) {
      // TODO Allow submitting non-current revision by changing the current.
      throw new ResourceConflictException(
          String.format(
              "revision %s is not current revision", rsrc.getPatchSet().commitId().name()));
    }

    try (MergeOp op = mergeOpProvider.get()) {
      Change updatedChange;

      updatedChange = op.merge(change, submitter, true, input, false);
      if (updatedChange.isMerged()) {
        return updatedChange;
      }

      throw new IllegalStateException(
          String.format(
              "change %s of project %s unexpectedly had status %s after submit attempt",
              updatedChange.getId(), updatedChange.getProject(), updatedChange.getStatus()));
    }
  }

  /**
   * Returns a message describing what prevents the current change from being submitted - or null.
   * This method only considers parent changes, and changes in the same topic. The caller is
   * responsible for making sure the current change to be submitted can indeed be submitted
   * (permissions, submit rules, is not a WIP...)
   *
   * @param cd the change the user is currently looking at
   * @param cs set of changes to be submitted at once
   * @param user the user who is checking to submit
   * @return a reason why any of the changes is not submittable or null
   */
  @Nullable
  private String problemsForSubmittingChangeset(ChangeData cd, ChangeSet cs, CurrentUser user) {
    try {
      if (cs.furtherHiddenChanges()) {
        logger.atFine().log(
            "Change %d cannot be submitted by user %s because it depends on hidden changes: %s",
            cd.getId().get(), user.getLoggableName(), cs.nonVisibleChanges());
        return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
      }
      for (ChangeData c : cs.changes()) {
        Set<ChangePermission> can =
            permissionBackend
                .user(user)
                .change(c)
                .test(EnumSet.of(ChangePermission.READ, ChangePermission.SUBMIT));
        if (!can.contains(ChangePermission.READ)) {
          logger.atFine().log(
              "Change %d cannot be submitted by user %s because it depends on change %d which the user cannot read",
              cd.getId().get(), user.getLoggableName(), c.getId().get());
          return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
        }
        if (!can.contains(ChangePermission.SUBMIT)) {
          return "You don't have permission to submit change " + c.getId();
        }
        if (c.change().isWorkInProgress()) {
          return "Change " + c.getId() + " is marked work in progress";
        }
        try {
          // The data in the change index may be stale (e.g. if submit requirements have been
          // changed). For that one change for which the submit action is computed, use the
          // freshly loaded ChangeData instance 'cd' instead of the potentially stale ChangeData
          // instance 'c' that was loaded from the index. This makes a difference if the ChangeSet
          // 'cs' only contains this one single change. If the ChangeSet contains further changes
          // those may still be stale.
          MergeOp.checkSubmitRequirements(cd.getId().equals(c.getId()) ? cd : c);
        } catch (ResourceConflictException e) {
          return (c.getId() == cd.getId())
              ? String.format("Change %s is not ready: %s", cd.getId(), e.getMessage())
              : String.format(
                  "Change %s must be submitted with change %s but %s is not ready: %s",
                  cd.getId(), c.getId(), c.getId(), e.getMessage());
        }
      }

      Collection<ChangeData> unmergeable = unmergeableChanges(cs);
      if (unmergeable == null) {
        return CLICK_FAILURE_TOOLTIP;
      } else if (!unmergeable.isEmpty()) {
        for (ChangeData c : unmergeable) {
          if (c.change().getKey().equals(cd.change().getKey())) {
            return CHANGE_UNMERGEABLE;
          }
        }

        return "Problems with change(s): "
            + unmergeable.stream().map(c -> c.getId().toString()).collect(joining(", "));
      }
    } catch (PermissionBackendException | IOException e) {
      logger.atSevere().withCause(e).log("Error checking if change is submittable");
      throw new StorageException("Could not determine problems for the change", e);
    }
    return null;
  }

  @Nullable
  @Override
  public UiAction.Description getDescription(RevisionResource resource)
      throws IOException, PermissionBackendException {
    Change change = resource.getChange();
    if (!change.isNew() || !resource.isCurrent()) {
      return null; // submit not visible
    }
    if (!projectCache
        .get(resource.getProject())
        .map(ProjectState::statePermitsWrite)
        .orElse(false)) {
      return null; // submit not visible
    }

    ChangeData cd = resource.getChangeResource().getChangeData();
    try {
      MergeOp.checkSubmitRequirements(cd);
    } catch (ResourceConflictException e) {
      return null; // submit not visible
    }

    ChangeSet cs =
        mergeSuperSet
            .get()
            .completeChangeSet(cd.change(), resource.getUser(), /*includingTopicClosure= */ false);
    String topic = change.getTopic();
    int topicSize = 0;
    if (!Strings.isNullOrEmpty(topic)) {
      topicSize = queryProvider.get().noFields().byTopicOpen(topic).size();
    }
    boolean treatWithTopic = submitWholeTopic && !Strings.isNullOrEmpty(topic) && topicSize > 1;

    String submitProblems = problemsForSubmittingChangeset(cd, cs, resource.getUser());

    if (submitProblems != null) {
      return new UiAction.Description()
          .setLabel(treatWithTopic ? submitTopicLabel : (cs.size() > 1) ? labelWithParents : label)
          .setTitle(submitProblems)
          .setVisible(true)
          .setEnabled(false);
    }

    // Recheck mergeability rather than using value stored in the index, which may be stale.
    // TODO(dborowitz): This is ugly; consider providing a way to not read stored fields from the
    // index in the first place.
    // cd.setMergeable(null);
    // That was done in unmergeableChanges which was called by problemsForSubmittingChangeset, so
    // now it is safe to read from the cache, as it yields the same result.
    Boolean enabled = cd.isMergeable();

    if (treatWithTopic) {
      Map<String, String> params =
          ImmutableMap.of(
              "topicSize", String.valueOf(topicSize),
              "submitSize", String.valueOf(cs.size()));
      return new UiAction.Description()
          .setLabel(submitTopicLabel)
          .setTitle(Strings.emptyToNull(submitTopicTooltip.replace(params)))
          .setVisible(true)
          .setEnabled(Boolean.TRUE.equals(enabled));
    }
    Map<String, String> params =
        ImmutableMap.of(
            "patchSet", String.valueOf(resource.getPatchSet().number()),
            "branch", change.getDest().shortName(),
            "commit", abbreviateName(resource.getPatchSet().commitId()),
            "submitSize", String.valueOf(cs.size()));
    ParameterizedString tp = cs.size() > 1 ? titlePatternWithAncestors : titlePattern;
    return new UiAction.Description()
        .setLabel(cs.size() > 1 ? labelWithParents : label)
        .setTitle(Strings.emptyToNull(tp.replace(params)))
        .setVisible(true)
        .setEnabled(Boolean.TRUE.equals(enabled));
  }

  @Nullable
  public Collection<ChangeData> unmergeableChanges(ChangeSet cs) throws IOException {
    Set<ChangeData> mergeabilityMap = new HashSet<>();
    Set<ObjectId> outDatedPatchsets = new HashSet<>();
    for (ChangeData change : cs.changes()) {
      mergeabilityMap.add(change);
      // Add all the patchsets commit ids except the current patchset.
      outDatedPatchsets.addAll(
          change.notes().getPatchSets().values().stream()
              .map(p -> p.commitId())
              .collect(Collectors.toSet()));
      outDatedPatchsets.remove(change.currentPatchSet().commitId());
    }

    ListMultimap<BranchNameKey, ChangeData> cbb = cs.changesByBranch();
    for (BranchNameKey branch : cbb.keySet()) {
      Collection<ChangeData> targetBranch = cbb.get(branch);
      HashMap<Change.Id, RevCommit> commits = findCommits(targetBranch, branch.project());

      Set<ObjectId> allParents = Sets.newHashSetWithExpectedSize(cs.size());
      for (RevCommit commit : commits.values()) {
        for (RevCommit parent : commit.getParents()) {
          allParents.add(parent.getId());
        }
      }
      for (ChangeData change : targetBranch) {

        RevCommit commit = commits.get(change.getId());
        boolean isMergeCommit = commit.getParentCount() > 1;
        boolean isLastInChain = !allParents.contains(commit.getId());
        if (Arrays.stream(commit.getParents()).anyMatch(c -> outDatedPatchsets.contains(c.getId()))
            && !isCherryPickSubmit(change)) {
          // Found a parent that depends on an outdated patchset and the submit strategy is not
          // cherry-pick.
          continue;
        }
        // Recheck mergeability rather than using value stored in the index,
        // which may be stale.
        // TODO(dborowitz): This is ugly; consider providing a way to not read
        // stored fields from the index in the first place.
        change.setMergeable(null);
        Boolean mergeable = change.isMergeable();
        if (mergeable == null) {
          // Skip whole check, cannot determine if mergeable
          return null;
        }
        if (mergeable) {
          mergeabilityMap.remove(change);
        }

        if (isLastInChain && isMergeCommit && mergeable) {
          for (ChangeData c : targetBranch) {
            mergeabilityMap.remove(c);
          }
          break;
        }
      }
    }
    return mergeabilityMap;
  }

  private boolean isCherryPickSubmit(ChangeData changeData) {
    SubmitTypeRecord submitTypeRecord = changeData.submitTypeRecord();
    return submitTypeRecord.isOk() && submitTypeRecord.type == SubmitType.CHERRY_PICK;
  }

  private HashMap<Change.Id, RevCommit> findCommits(
      Collection<ChangeData> changes, Project.NameKey project) throws IOException {
    HashMap<Change.Id, RevCommit> commits = new HashMap<>();
    try (Repository repo = repoManager.openRepository(project);
        RevWalk walk = new RevWalk(repo)) {
      for (ChangeData change : changes) {
        RevCommit commit = walk.parseCommit(psUtil.current(change.notes()).commitId());
        commits.put(change.getId(), commit);
      }
    }
    return commits;
  }

  private IdentifiedUser onBehalfOf(RevisionResource rsrc, SubmitInput in)
      throws AuthException, UnprocessableEntityException, PermissionBackendException, IOException,
          ConfigInvalidException {
    PermissionBackend.ForChange perm = rsrc.permissions();
    perm.check(ChangePermission.SUBMIT);
    perm.check(ChangePermission.SUBMIT_AS);

    CurrentUser caller = rsrc.getUser();
    IdentifiedUser submitter =
        accountResolver.resolve(in.onBehalfOf).asUniqueUserOnBehalfOf(caller);
    try {
      permissionBackend.user(submitter).change(rsrc.getNotes()).check(ChangePermission.READ);
    } catch (AuthException e) {
      throw new UnprocessableEntityException(
          String.format("on_behalf_of account %s cannot see change", submitter.getAccountId()), e);
    }
    return submitter;
  }

  public static class CurrentRevision implements RestModifyView<ChangeResource, SubmitInput> {
    private final Submit submit;
    private final PatchSetUtil psUtil;

    @Inject
    CurrentRevision(Submit submit, PatchSetUtil psUtil) {
      this.submit = submit;
      this.psUtil = psUtil;
    }

    @Override
    public Response<ChangeInfo> apply(ChangeResource rsrc, SubmitInput input) throws Exception {
      PatchSet ps = psUtil.current(rsrc.getNotes());
      if (ps == null) {
        throw new ResourceConflictException("current revision is missing");
      }

      return submit.apply(new RevisionResource(rsrc, ps), input);
    }
  }
}