text:
package org.opencb.opencga.master.monitor.daemons;

import com.google.common.base.CaseFormat;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.glassfish.jersey.client.ClientProperties;
import org.opencb.commons.datastore.core.Event;
import org.opencb.commons.datastore.core.ObjectMap;
import org.opencb.commons.datastore.core.Query;
import org.opencb.commons.datastore.core.QueryOptions;
import org.opencb.commons.utils.ListUtils;
import org.opencb.opencga.analysis.clinical.zetta.ZettaInterpretationAnalysis;
import org.opencb.opencga.analysis.clinical.team.TeamInterpretationAnalysis;
import org.opencb.opencga.analysis.clinical.tiering.CancerTieringInterpretationAnalysis;
import org.opencb.opencga.analysis.clinical.tiering.TieringInterpretationAnalysis;
import org.opencb.opencga.analysis.cohort.CohortIndexTask;
import org.opencb.opencga.analysis.cohort.CohortTsvAnnotationLoader;
import org.opencb.opencga.analysis.family.FamilyIndexTask;
import org.opencb.opencga.analysis.family.FamilyTsvAnnotationLoader;
import org.opencb.opencga.analysis.file.FetchAndRegisterTask;
import org.opencb.opencga.analysis.file.FileDeleteTask;
import org.opencb.opencga.analysis.file.FileIndexTask;
import org.opencb.opencga.analysis.file.FileTsvAnnotationLoader;
import org.opencb.opencga.analysis.individual.IndividualIndexTask;
import org.opencb.opencga.analysis.individual.IndividualTsvAnnotationLoader;
import org.opencb.opencga.analysis.job.JobIndexTask;
import org.opencb.opencga.analysis.sample.SampleIndexTask;
import org.opencb.opencga.analysis.sample.SampleTsvAnnotationLoader;
import org.opencb.opencga.analysis.tools.ToolFactory;
import org.opencb.opencga.analysis.variant.VariantExportTool;
import org.opencb.opencga.analysis.variant.geneticChecks.GeneticChecksAnalysis;
import org.opencb.opencga.analysis.variant.gwas.GwasAnalysis;
import org.opencb.opencga.analysis.variant.inferredSex.InferredSexAnalysis;
import org.opencb.opencga.analysis.variant.knockout.KnockoutAnalysis;
import org.opencb.opencga.analysis.variant.mendelianError.MendelianErrorAnalysis;
import org.opencb.opencga.analysis.variant.mutationalSignature.MutationalSignatureAnalysis;
import org.opencb.opencga.analysis.variant.operations.*;
import org.opencb.opencga.analysis.variant.relatedness.RelatednessAnalysis;
import org.opencb.opencga.analysis.variant.samples.SampleEligibilityAnalysis;
import org.opencb.opencga.analysis.variant.samples.SampleVariantFilterAnalysis;
import org.opencb.opencga.analysis.variant.stats.CohortVariantStatsAnalysis;
import org.opencb.opencga.analysis.variant.stats.SampleVariantStatsAnalysis;
import org.opencb.opencga.analysis.variant.stats.VariantStatsAnalysis;
import org.opencb.opencga.analysis.wrappers.*;
import org.opencb.opencga.catalog.db.api.DBIterator;
import org.opencb.opencga.catalog.db.api.FileDBAdaptor;
import org.opencb.opencga.catalog.db.api.JobDBAdaptor;
import org.opencb.opencga.catalog.db.api.StudyDBAdaptor;
import org.opencb.opencga.catalog.exceptions.CatalogDBException;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.catalog.exceptions.CatalogIOException;
import org.opencb.opencga.catalog.io.IOManager;
import org.opencb.opencga.catalog.managers.CatalogManager;
import org.opencb.opencga.catalog.managers.FileManager;
import org.opencb.opencga.catalog.managers.JobManager;
import org.opencb.opencga.catalog.utils.Constants;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.core.common.JacksonUtils;
import org.opencb.opencga.core.common.TimeUtils;
import org.opencb.opencga.core.exceptions.ToolException;
import org.opencb.opencga.core.models.AclParams;
import org.opencb.opencga.core.models.common.Enums;
import org.opencb.opencga.core.models.file.File;
import org.opencb.opencga.core.models.file.FileAclEntry;
import org.opencb.opencga.core.models.file.FileAclParams;
import org.opencb.opencga.core.models.job.*;
import org.opencb.opencga.core.models.study.Group;
import org.opencb.opencga.core.models.study.Study;
import org.opencb.opencga.core.response.OpenCGAResult;
import org.opencb.opencga.core.tools.annotations.Tool;
import org.opencb.opencga.core.tools.result.ExecutionResult;
import org.opencb.opencga.core.tools.result.ExecutionResultManager;
import org.opencb.opencga.core.tools.result.Status;
import org.opencb.opencga.master.monitor.models.PrivateJobUpdateParams;

import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by imedina on 16/06/16.
 */
public class ExecutionDaemon extends MonitorParentDaemon {

    public static final String OUTDIR_PARAM = "outdir";
    public static final int EXECUTION_RESULT_FILE_EXPIRATION_MINUTES = 10;
    private String internalCli;
    private JobManager jobManager;
    private FileManager fileManager;
    private final Map<String, Long> jobsCountByType = new HashMap<>();
    private final Map<String, Long> retainedLogsTime = new HashMap<>();

    private Path defaultJobDir;

    private static final Map<String, String> TOOL_CLI_MAP;

    // Maximum number of jobs of each type (Pending, queued, running) that will be handled on each iteration.
    // Example: If there are 100 pending jobs, 15 queued, 70 running.
    // On first iteration, it will queue 50 out of the 100 pending jobs. It will check up to 50 queue-running changes out of the 65
    // (15 + 50 from pending), and it will check up to 50 finished jobs from the running ones.
    // On second iteration, it will queue the remaining 50 pending jobs, and so on...
    private static final int NUM_JOBS_HANDLED = 50;
    private final Query pendingJobsQuery;
    private final Query queuedJobsQuery;
    private final Query runningJobsQuery;
    private final QueryOptions queryOptions;

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    static {
        TOOL_CLI_MAP = new HashMap<String, String>(){{
            put("files-unlink", "files unlink");
            put(FileDeleteTask.ID, "files delete");
            put(FetchAndRegisterTask.ID, "files fetch");
            put(FileIndexTask.ID, "files secondary-index");
            put(FileTsvAnnotationLoader.ID, "files tsv-load");

            put(SampleIndexTask.ID, "samples secondary-index");
            put(SampleTsvAnnotationLoader.ID, "samples tsv-load");

            put(IndividualIndexTask.ID, "individuals secondary-index");
            put(IndividualTsvAnnotationLoader.ID, "individuals tsv-load");

            put(CohortIndexTask.ID, "cohorts secondary-index");
            put(CohortTsvAnnotationLoader.ID, "cohorts tsv-load");

            put(FamilyIndexTask.ID, "families secondary-index");
            put(FamilyTsvAnnotationLoader.ID, "families tsv-load");

            put(JobIndexTask.ID, "jobs secondary-index");

            put("alignment-index", "alignment index");
            put("alignment-coverage-run", "alignment coverage-run");
            put("alignment-stats-run", "alignment stats-run");
            put(BwaWrapperAnalysis.ID, "alignment " + BwaWrapperAnalysis.ID + "-run");
            put(SamtoolsWrapperAnalysis.ID, "alignment " + SamtoolsWrapperAnalysis.ID + "-run");
            put(DeeptoolsWrapperAnalysis.ID, "alignment " + DeeptoolsWrapperAnalysis.ID + "-run");
            put(FastqcWrapperAnalysis.ID, "alignment " + FastqcWrapperAnalysis.ID + "-run");

            put(VariantIndexOperationTool.ID, "variant index");
            put(VariantExportTool.ID, "variant export");
            put(VariantStatsAnalysis.ID, "variant stats-run");
            put("variant-stats-export", "variant stats-export");
            put(SampleVariantStatsAnalysis.ID, "variant sample-stats-run");
            put(CohortVariantStatsAnalysis.ID, "variant cohort-stats-run");
            put(GwasAnalysis.ID, "variant gwas-run");
            put(PlinkWrapperAnalysis.ID, "variant " + PlinkWrapperAnalysis.ID + "-run");
            put(RvtestsWrapperAnalysis.ID, "variant " + RvtestsWrapperAnalysis.ID + "-run");
            put(GatkWrapperAnalysis.ID, "variant " + GatkWrapperAnalysis.ID + "-run");
            put(VariantFileDeleteOperationTool.ID, "variant file-delete");
            put(VariantSecondaryIndexOperationTool.ID, "variant secondary-index");
            put(VariantSecondaryIndexSamplesDeleteOperationTool.ID, "variant secondary-index-delete");
            put(VariantScoreDeleteOperationTool.ID, "variant score-delete");
            put(VariantScoreIndexOperationTool.ID, "variant score-index");
            put(VariantSampleIndexOperationTool.ID, "variant sample-index");
            put(VariantFamilyIndexOperationTool.ID, "variant family-index");
            put(VariantAggregateFamilyOperationTool.ID, "variant aggregate-family");
            put(VariantAggregateOperationTool.ID, "variant aggregate");
            put(VariantAnnotationIndexOperationTool.ID, "variant annotation-index");
            put(VariantAnnotationDeleteOperationTool.ID, "variant annotation-delete");
            put(VariantAnnotationSaveOperationTool.ID, "variant annotation-save");
            put(SampleVariantFilterAnalysis.ID, "variant sample-run");
            put(KnockoutAnalysis.ID, "variant knockout-run");
            put(SampleEligibilityAnalysis.ID, "variant " + SampleEligibilityAnalysis.ID + "-run");
            put(MutationalSignatureAnalysis.ID, "variant " + MutationalSignatureAnalysis.ID + "-run");
            put(MendelianErrorAnalysis.ID, "variant " + MendelianErrorAnalysis.ID + "-run");
            put(InferredSexAnalysis.ID, "variant " + InferredSexAnalysis.ID + "-run");
            put(RelatednessAnalysis.ID, "variant " + RelatednessAnalysis.ID + "-run");
            put(GeneticChecksAnalysis.ID, "variant " + GeneticChecksAnalysis.ID + "-run");

            put(TeamInterpretationAnalysis.ID, "clinical " + TeamInterpretationAnalysis.ID + "-run");
            put(TieringInterpretationAnalysis.ID, "clinical " + TieringInterpretationAnalysis.ID + "-run");
            put(ZettaInterpretationAnalysis.ID, "clinical " + ZettaInterpretationAnalysis.ID + "-run");
            put(CancerTieringInterpretationAnalysis.ID, "clinical " + CancerTieringInterpretationAnalysis.ID + "-run");
        }};
    }

    public ExecutionDaemon(int interval, String token, CatalogManager catalogManager, String appHome) throws CatalogDBException {
        super(interval, token, catalogManager);

        this.jobManager = catalogManager.getJobManager();
        this.fileManager = catalogManager.getFileManager();
        this.internalCli = appHome + "/bin/opencga-internal.sh";

        this.defaultJobDir = Paths.get(catalogManager.getConfiguration().getJobDir());

        pendingJobsQuery = new Query(JobDBAdaptor.QueryParams.INTERNAL_STATUS_NAME.key(), Enums.ExecutionStatus.PENDING);
        queuedJobsQuery = new Query(JobDBAdaptor.QueryParams.INTERNAL_STATUS_NAME.key(), Enums.ExecutionStatus.QUEUED);
        runningJobsQuery = new Query(JobDBAdaptor.QueryParams.INTERNAL_STATUS_NAME.key(), Enums.ExecutionStatus.RUNNING);
        // Sort jobs by priority and creation date
        queryOptions = new QueryOptions()
                .append(QueryOptions.SORT, Arrays.asList(JobDBAdaptor.QueryParams.PRIORITY.key(),
                        JobDBAdaptor.QueryParams.CREATION_DATE.key()))
                .append(QueryOptions.ORDER, QueryOptions.ASCENDING);
    }

    @Override
    public void run() {
        while (!exit) {
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                if (!exit) {
                    e.printStackTrace();
                }
            }

            try {
                checkJobs();
            } catch (Exception e) {
                logger.error("Catch exception " + e.getMessage(), e);
            }
        }

        try {
            logger.info("Attempt to shutdown webhook executor");
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            logger.error("Webhook tasks interrupted");
        } finally {
            if (!executor.isTerminated()) {
                logger.error("Cancel non-finished webhook tasks");
            }
            executor.shutdownNow();
            logger.info("Webhook tasks finished");
        }
    }

    protected void checkJobs() {
        long pendingJobs = -1;
        long queuedJobs = -1;
        long runningJobs = -1;
        try {
            pendingJobs = jobManager.count(pendingJobsQuery, token).getNumMatches();
            queuedJobs = jobManager.count(queuedJobsQuery, token).getNumMatches();
            runningJobs = jobManager.count(runningJobsQuery, token).getNumMatches();
        } catch (CatalogException e) {
            logger.error("{}", e.getMessage(), e);
        }
        logger.info("----- EXECUTION DAEMON  ----- pending={}, queued={}, running={}", pendingJobs, queuedJobs, runningJobs);

            /*
            PENDING JOBS
             */
        checkPendingJobs();

            /*
            QUEUED JOBS
             */
        checkQueuedJobs();

            /*
            RUNNING JOBS
             */
        checkRunningJobs();
    }

    protected void checkRunningJobs() {
        int handledRunningJobs = 0;
        try (DBIterator<Job> iterator = jobManager.iterator(runningJobsQuery, queryOptions, token)) {
            while (handledRunningJobs < NUM_JOBS_HANDLED && iterator.hasNext()) {
                Job job = iterator.next();
                handledRunningJobs += checkRunningJob(job);
            }
        } catch (CatalogException e) {
            logger.error("{}", e.getMessage(), e);
        }
    }

    protected int checkRunningJob(Job job) {
        Enums.ExecutionStatus jobStatus = getCurrentStatus(job);

        switch (jobStatus.getName()) {
            case Enums.ExecutionStatus.RUNNING:
                ExecutionResult result = readAnalysisResult(job);
                if (result != null) {
                    // Update the result of the job
                    PrivateJobUpdateParams updateParams = new PrivateJobUpdateParams().setExecution(result);
                    try {
                        jobManager.update(job.getStudy().getId(), job.getId(), updateParams, QueryOptions.empty(), token);
                    } catch (CatalogException e) {
                        logger.error("[{}] - Could not update result information: {}", job.getId(), e.getMessage(), e);
                        return 0;
                    }
                }
                return 1;
            case Enums.ExecutionStatus.ABORTED:
            case Enums.ExecutionStatus.ERROR:
            case Enums.ExecutionStatus.DONE:
            case Enums.ExecutionStatus.READY:
                // Register job results
                return processFinishedJob(job, jobStatus);
            case Enums.ExecutionStatus.QUEUED:
                // Running job went back to Queued?
                logger.info("Running job '{}' went back to '{}' status", job.getId(), jobStatus.getName());
                return setStatus(job, new Enums.ExecutionStatus(Enums.ExecutionStatus.QUEUED));
            case Enums.ExecutionStatus.PENDING:
            case Enums.ExecutionStatus.UNKNOWN:
            default:
                logger.info("Unexpected status '{}' for job '{}'", jobStatus.getName(), job.getId());
                return 0;

        }
    }

    protected void checkQueuedJobs() {
        int handledQueuedJobs = 0;
        try (DBIterator<Job> iterator = jobManager.iterator(queuedJobsQuery, queryOptions, token)) {
            while (handledQueuedJobs < NUM_JOBS_HANDLED && iterator.hasNext()) {
                Job job = iterator.next();
                handledQueuedJobs += checkQueuedJob(job);
            }
        } catch (CatalogException e) {
            logger.error("{}", e.getMessage(), e);
        }
    }

    /**
     * Check if the job is still queued or it has changed to running or error.
     *
     * @param job Job object.
     * @return 1 if the job has changed the status, 0 otherwise.
     */
    protected int checkQueuedJob(Job job) {
        Enums.ExecutionStatus status = getCurrentStatus(job);

        switch (status.getName()) {
            case Enums.ExecutionStatus.QUEUED:
                // Job is still queued
                return 0;
            case Enums.ExecutionStatus.RUNNING:
                logger.info("[{}] - Updating status from {} to {}", job.getId(),
                        Enums.ExecutionStatus.QUEUED, Enums.ExecutionStatus.RUNNING);
                logger.info("[{}] - stdout file '{}'", job.getId(),
                        job.getOutDir().getUri().resolve(getLogFileName(job)).getPath());
                logger.info("[{}] - stderr file: '{}'", job.getId(),
                        job.getOutDir().getUri().resolve(getErrorLogFileName(job)).getPath());
                return setStatus(job, new Enums.ExecutionStatus(Enums.ExecutionStatus.RUNNING));
            case Enums.ExecutionStatus.ABORTED:
            case Enums.ExecutionStatus.ERROR:
            case Enums.ExecutionStatus.DONE:
            case Enums.ExecutionStatus.READY:
                // Job has finished the execution, so we need to register the job results
                return processFinishedJob(job, status);
            case Enums.ExecutionStatus.UNKNOWN:
                logger.info("Job '{}' in status {}", job.getId(), Enums.ExecutionStatus.UNKNOWN);
                return 0;
            default:
                logger.info("Unexpected status '{}' for job '{}'", status.getName(), job.getId());
                return 0;
        }
    }

    protected void checkPendingJobs() {
        // Clear job counts each cycle
        jobsCountByType.clear();

        int handledPendingJobs = 0;
        try (DBIterator<Job> iterator = jobManager.iterator(pendingJobsQuery, queryOptions, token)) {
            while (handledPendingJobs < NUM_JOBS_HANDLED && iterator.hasNext()) {
                Job job = iterator.next();
                handledPendingJobs += checkPendingJob(job);
            }
        } catch (CatalogException e) {
            logger.error("{}", e.getMessage(), e);
        }
    }

    /**
     * Check everything is correct and queues the job.
     *
     * @param job Job object.
     * @return 1 if the job has changed the status, 0 otherwise.
     */
    protected int checkPendingJob(Job job) {
        if (StringUtils.isEmpty(job.getStudy().getId())) {
            return abortJob(job, "Missing mandatory 'studyUuid' field");
        }

        if (StringUtils.isEmpty(job.getTool().getId()) || !TOOL_CLI_MAP.containsKey(job.getTool().getId())) {
            return abortJob(job, "Tool id '" + job.getTool().getId() + "' not found.");
        }

        if (!canBeQueued(job)) {
            return 0;
        }

        Tool tool;
        try {
            tool = new ToolFactory().getTool(job.getTool().getId());
        } catch (ToolException e) {
            logger.error(e.getMessage(), e);
            return abortJob(job, "Tool " + job.getTool().getId() + " not found");
        }

        PrivateJobUpdateParams updateParams = new PrivateJobUpdateParams();
        updateParams.setTool(new ToolInfo(tool.id(), tool.description(), tool.scope(), tool.type(), tool.resource()));

        if (tool.scope() == Tool.Scope.PROJECT) {
            String projectFqn = job.getStudy().getId().substring(0, job.getStudy().getId().indexOf(":"));
            OpenCGAResult<Study> studyResult;
            try {
                studyResult = catalogManager.getStudyManager().get(projectFqn, new Query(),
                        new QueryOptions(QueryOptions.INCLUDE, Arrays.asList(StudyDBAdaptor.QueryParams.GROUPS.key(),
                                StudyDBAdaptor.QueryParams.FQN.key())), token);
            } catch (CatalogException e) {
                logger.error(e.getMessage(), e);
                return abortJob(job, e.getMessage());
            }
            // Validate user is owner or admin
            if (!job.getStudy().getId().startsWith(job.getUserId() + "@")) {
                // It is not the owner, so we check if it is the admin
                for (Study study : studyResult.getResults()) {
                    for (Group group : study.getGroups()) {
                        if (group.getId().equals("@admins")) {
                            // If the user does not belong to the admins group
                            if (!group.getUserIds().contains(job.getUserId())) {
                                return abortJob(job, "User '" + job.getUserId() + "' is not owner or admin of study '" + study.getFqn()
                                        + "'. The tool '" + job.getTool().getId()
                                        + "' can only be executed by the project owners or admins");
                            }
                            break;
                        }
                    }
                }
            }

            // Obtain other study fqns
            Set<String> studyFqnSet = studyResult.getResults().stream()
                    .map(Study::getFqn)
                    .filter(fqn -> !fqn.equals(job.getStudy().getId()))
                    .collect(Collectors.toSet());
            updateParams.setStudy(new JobStudyParam(job.getStudy().getId(), new ArrayList<>(studyFqnSet)));
        }

        String userToken;
        try {
            userToken = catalogManager.getUserManager().getNonExpiringToken(job.getUserId(), token);
        } catch (CatalogException e) {
            return abortJob(job, "Internal error. Could not obtain token for user '" + job.getUserId() + "'");
        }

        if (CollectionUtils.isNotEmpty(job.getDependsOn())) {
            // The job(s) it depended on finished successfully. Check if the input files are correct.
            // Look for input files
            String fileParamSuffix = "file";
            List<File> inputFiles = new ArrayList<>();
            if (job.getParams() != null) {
                Map<String, Object> dynamicParams = null;
                for (Map.Entry<String, Object> entry : job.getParams().entrySet()) {
                    // We assume that every variable ending in 'file' corresponds to input files that need to be accessible in catalog
                    if (entry.getKey().toLowerCase().endsWith(fileParamSuffix)) {
                        for (String fileStr : StringUtils.split((String) entry.getValue(), ',')) {
                            try {
                                // Validate the user has access to the file
                                File file = catalogManager.getFileManager().get(job.getStudy().getId(), fileStr,
                                        FileManager.INCLUDE_FILE_URI_PATH, token).first();
                                inputFiles.add(file);
                            } catch (CatalogException e) {
                                String msg = "Cannot find file '" + entry.getValue() + "' "
                                        + "from job param '" + entry.getKey() + "'; (study = " + job.getStudy().getId() + ", token = "
                                        + token + ") :" + e.getMessage();
                                logger.error(msg, e);
                                return abortJob(job, msg);
                            }
                        }
                    } else if (entry.getValue() instanceof Map) {
                        if (dynamicParams != null) {
                            List<String> dynamicParamKeys = job.getParams()
                                    .entrySet()
                                    .stream()
                                    .filter(e -> e.getValue() instanceof Map)
                                    .map(Map.Entry::getKey)
                                    .collect(Collectors.toList());

                            String msg = "Found multiple dynamic param maps in job params: " + dynamicParamKeys;
                            logger.error(msg);
                            return abortJob(job, msg);
                        }
                        // If we have found a map for further dynamic params...
                        dynamicParams = (Map<String, Object>) entry.getValue();
                    }
                }
                if (dynamicParams != null) {
                    // We look for files in the dynamic params
                    for (Map.Entry<String, Object> entry : dynamicParams.entrySet()) {
                        if (entry.getKey().toLowerCase().endsWith(fileParamSuffix)) {
                            // We assume that every variable ending in 'file' corresponds to input files that need to be accessible in
                            // catalog
                            try {
                                // Validate the user has access to the file
                                File file = catalogManager.getFileManager().get(job.getStudy().getId(), (String) entry.getValue(),
                                        FileManager.INCLUDE_FILE_URI_PATH, token).first();
                                inputFiles.add(file);
                            } catch (CatalogException e) {
                                String msg = "Cannot find file '" + entry.getValue() + "' from variable '" + entry.getKey() + "'. ";
                                logger.error(msg, e);
                                return abortJob(job, msg);
                            }
                        }
                    }
                }
            }
            updateParams.setInput(inputFiles);
        }

        Map<String, Object> params = job.getParams();
        String outDirPathParam = (String) params.get(OUTDIR_PARAM);
        if (!StringUtils.isEmpty(outDirPathParam)) {
            try {
                // Any path the user has requested
                updateParams.setOutDir(getValidInternalOutDir(job.getStudy().getId(), job, outDirPathParam, userToken));
            } catch (CatalogException e) {
                logger.error("Cannot create output directory. {}", e.getMessage(), e);
                return abortJob(job, "Cannot create output directory. " + e.getMessage());
            }
        } else {
            try {
                // JOBS/user/job_id/
                updateParams.setOutDir(getValidDefaultOutDir(job));
            } catch (CatalogException e) {
                logger.error("Cannot create output directory. {}", e.getMessage(), e);
                return abortJob(job, "Cannot create output directory. " + e.getMessage());
            }
        }

        Path outDirPath = Paths.get(updateParams.getOutDir().getUri());
        params.put(OUTDIR_PARAM, outDirPath.toAbsolutePath().toString());

        // Define where the stdout and stderr will be stored
        Path stderr = outDirPath.resolve(getErrorLogFileName(job));
        Path stdout = outDirPath.resolve(getLogFileName(job));

        // Create cli
        String commandLine = buildCli(internalCli, job.getTool().getId(), params);
        String authenticatedCommandLine = commandLine + " --token " + userToken;
        String shadedCommandLine = commandLine + " --token xxxxxxxxxxxxxxxxxxxxx";

        updateParams.setCommandLine(shadedCommandLine);

        logger.info("Updating job {} from {} to {}", job.getId(), Enums.ExecutionStatus.PENDING, Enums.ExecutionStatus.QUEUED);
        updateParams.setInternal(new JobInternal(new Enums.ExecutionStatus(Enums.ExecutionStatus.QUEUED)));
        try {
            jobManager.update(job.getStudy().getId(), job.getId(), updateParams, QueryOptions.empty(), token);
        } catch (CatalogException e) {
            logger.error("Could not update job {}. {}", job.getId(), e.getMessage(), e);
            return 0;
        }

        try {
            batchExecutor.execute(job.getId(), authenticatedCommandLine, stdout, stderr);
        } catch (Exception e) {
            logger.error("Error executing job {}.", job.getId(), e);
            return abortJob(job, "Error executing job. " + e.getMessage());
        }

        job.getInternal().setStatus(updateParams.getInternal().getStatus());
        notifyStatusChange(job);

        return 1;
    }

    private File getValidInternalOutDir(String study, Job job, String outDirPath, String userToken) throws CatalogException {
        // TODO: Remove this line when we stop passing the outdir as a query param in the URL
        outDirPath = outDirPath.replace(":", "/");
        if (!outDirPath.endsWith("/")) {
            outDirPath += "/";
        }
        File outDir;
        try {
            outDir = fileManager.get(study, outDirPath, FileManager.INCLUDE_FILE_URI_PATH, token).first();
        } catch (CatalogException e) {
            // Directory not found. Will try to create using user's token
            boolean parents = (boolean) job.getAttributes().getOrDefault(Job.OPENCGA_PARENTS, false);
            try {
                outDir = fileManager.createFolder(study, outDirPath, parents, "", FileManager.INCLUDE_FILE_URI_PATH,
                        userToken).first();
                IOManager ioManager = catalogManager.getIoManagerFactory().get(outDir.getUri());
                ioManager.createDirectory(outDir.getUri(), true);
            } catch (CatalogException | IOException e1) {
                throw new CatalogException("Cannot create output directory. " + e1.getMessage(), e1.getCause());
            }
        }

        // Ensure the directory is empty
        IOManager ioManager;
        try {
            ioManager = catalogManager.getIoManagerFactory().get(outDir.getUri());
        } catch (IOException e) {
            throw CatalogIOException.ioManagerException(outDir.getUri(), e);
        }
        if (!ioManager.isDirectory(outDir.getUri())) {
            throw new CatalogException(OUTDIR_PARAM + " seems not to be a directory");
        }
        if (!ioManager.listFiles(outDir.getUri()).isEmpty()) {
            throw new CatalogException(OUTDIR_PARAM + " " + outDirPath + " is not an empty directory");
        }

        return outDir;
    }

    private File getValidDefaultOutDir(Job job) throws CatalogException {
        File folder = fileManager.createFolder(job.getStudy().getId(), "JOBS/" + job.getUserId() + "/" + TimeUtils.getDay() + "/"
                        + job.getId(), true, "Job " + job.getTool().getId(), QueryOptions.empty(), token).first();

        // By default, OpenCGA will not create the physical folders until there is a file, so we need to create it manually
        try {
            catalogManager.getIoManagerFactory().get(folder.getUri()).createDirectory(folder.getUri(), true);
        } catch (CatalogIOException | IOException e) {
            // Submit job to delete job folder
            ObjectMap params = new ObjectMap()
                    .append("files", folder.getUuid())
                    .append("study", job.getStudy().getId())
                    .append(Constants.SKIP_TRASH, true);
            jobManager.submit(job.getStudy().getId(), FileDeleteTask.ID, Enums.Priority.LOW, params, token);
            throw new CatalogException("Cannot create job directory '" + folder.getUri() + "' for path '" + folder.getPath() + "'");
        }

        // Check if the user already has permissions set in his folder
        OpenCGAResult<Map<String, List<String>>> result = fileManager.getAcls(job.getStudy().getId(),
                Collections.singletonList("JOBS/" + job.getUserId() + "/"), job.getUserId(), true, token);
        if (result.getNumResults() == 0 || result.first().isEmpty() || ListUtils.isEmpty(result.first().get(job.getUserId()))) {
            // Add permissions to do anything under that path to the user launching the job
            String allFilePermissions = EnumSet.allOf(FileAclEntry.FilePermissions.class)
                    .stream()
                    .map(FileAclEntry.FilePermissions::toString)
                    .collect(Collectors.joining(","));
            fileManager.updateAcl(job.getStudy().getId(), Collections.singletonList("JOBS/" + job.getUserId() + "/"), job.getUserId(),
                    new FileAclParams(allFilePermissions, AclParams.Action.SET, null), token);
        }

        return folder;
    }

    public static String buildCli(String internalCli, String toolId, Map<String, Object> params) {
        StringBuilder cliBuilder = new StringBuilder()
                .append(internalCli)
                .append(" ").append(TOOL_CLI_MAP.get(toolId));
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (entry.getValue() instanceof Map) {
                Map<String, String> dynamicParams = (Map<String, String>) entry.getValue();
                for (Map.Entry<String, String> dynamicEntry : dynamicParams.entrySet()) {
                    cliBuilder.append(" ").append("-D");
                    escapeCliArg(cliBuilder, dynamicEntry.getKey());
                    cliBuilder.append("=");
                    escapeCliArg(cliBuilder, dynamicEntry.getValue());
                }
            } else {
                String key = entry.getKey();
                if (!StringUtils.isAlphanumeric(StringUtils.replaceChars(key, "-_", ""))) {
                    // This should never happen
                    throw new IllegalArgumentException("Invalid job param key '" + key + "'");
                }
                cliBuilder
                        .append(" --").append(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, key))
                        .append(" ");
                escapeCliArg(cliBuilder, entry.getValue().toString());
            }
        }
        return cliBuilder.toString();
    }

    /**
     * Escape args if needed.
     *
     * Surround with single quotes. ('value')
     * Detect if the value had any single quote, and escape them with double quotes ("'")
     *
     *   --description It's true
     *   --description 'It'"'"'s true'
     *
     * 'It'
     * "'"
     * 's true'
     *
     * @param cliBuilder CommandLine StringBuilder
     * @param value value to escape
     */
    public static void escapeCliArg(StringBuilder cliBuilder, String value) {
        if (StringUtils.isAlphanumeric(value) || StringUtils.isEmpty(value)) {
            cliBuilder.append(value);
        } else {
            if (value.contains("'")) {
                value = value.replace("'", "'\"'\"'");
            }
            cliBuilder.append("'").append(value).append("'");
        }
    }

    private boolean canBeQueued(Job job) {
        if (job.getDependsOn() != null && !job.getDependsOn().isEmpty()) {
            for (Job tmpJob : job.getDependsOn()) {
                if (!Enums.ExecutionStatus.DONE.equals(tmpJob.getInternal().getStatus().getName())) {
                    if (Enums.ExecutionStatus.ABORTED.equals(tmpJob.getInternal().getStatus().getName())
                            || Enums.ExecutionStatus.ERROR.equals(tmpJob.getInternal().getStatus().getName())) {
                        abortJob(job, "Job '" + tmpJob.getId() + "' it depended on did not finish successfully");
                    }
                    return false;
                }
            }
        }

        if (!batchExecutor.canBeQueued()) {
            return false;
        }

        switch (job.getTool().getId()) {
            case "variant-index":
                int maxIndexJobs = catalogManager.getConfiguration().getAnalysis().getIndex().getVariant().getMaxConcurrentJobs();
                return canBeQueued("variant-index", maxIndexJobs);
            default:
                return true;
        }
    }

    private boolean canBeQueued(String toolId, int maxJobs) {
        Query query = new Query()
                .append(JobDBAdaptor.QueryParams.INTERNAL_STATUS_NAME.key(), Enums.ExecutionStatus.QUEUED + ","
                        + Enums.ExecutionStatus.RUNNING)
                .append(JobDBAdaptor.QueryParams.TOOL_ID.key(), toolId);
        long currentJobs = jobsCountByType.computeIfAbsent(toolId, k -> {
            try {
                return catalogManager.getJobManager().count(query, token).getNumMatches();
            } catch (CatalogException e) {
                logger.error("Error counting the current number of running and queued \"" + toolId + "\" jobs", e);
                return 0L;
            }
        });
        if (currentJobs >= maxJobs) {
            long now = System.currentTimeMillis();
            Long lastTimeLog = retainedLogsTime.getOrDefault(toolId, 0L);
            if (now - lastTimeLog > 60000) {
                logger.info("There are {} " + toolId + " jobs running or queued already. "
                        + "Current limit is {}. "
                        + "Halt new " + toolId + " jobs.", currentJobs, maxJobs);
                retainedLogsTime.put(toolId, now);
            }
            return false;
        } else {
            jobsCountByType.remove(toolId);
            retainedLogsTime.put(toolId, 0L);
            return true;
        }
    }

    private int abortJob(Job job, String description) {
        logger.info("Aborting job: {} - Reason: '{}'", job.getId(), description);
        return setStatus(job, new Enums.ExecutionStatus(Enums.ExecutionStatus.ABORTED, description));
    }

    private int setStatus(Job job, Enums.ExecutionStatus status) {
        PrivateJobUpdateParams updateParams = new PrivateJobUpdateParams().setInternal(new JobInternal(status));

        try {
            jobManager.update(job.getStudy().getId(), job.getId(), updateParams, QueryOptions.empty(), token);
        } catch (CatalogException e) {
            logger.error("Unexpected error. Cannot update job '{}' to status '{}'. {}", job.getId(),
                    updateParams.getInternal().getStatus().getName(), e.getMessage(), e);
            return 0;
        }

        job.getInternal().setStatus(status);
        notifyStatusChange(job);

        return 1;
    }

    private Enums.ExecutionStatus getCurrentStatus(Job job) {

        Path resultJson = getAnalysisResultPath(job);

        // Check if analysis result file is there
        if (resultJson != null && Files.exists(resultJson)) {
            ExecutionResult execution = readAnalysisResult(resultJson);
            if (execution != null) {
                long lastStatusUpdate = execution.getStatus().getDate().getTime();
                long fileAgeInMillis = Instant.now().toEpochMilli() - lastStatusUpdate;
                if (TimeUnit.MILLISECONDS.toMinutes(fileAgeInMillis) > EXECUTION_RESULT_FILE_EXPIRATION_MINUTES) {
                    logger.warn("Ignoring file '" + resultJson + "'. The file is more than "
                            + EXECUTION_RESULT_FILE_EXPIRATION_MINUTES + " minutes old");
                } else {
                    return new Enums.ExecutionStatus(execution.getStatus().getName().name());
                }
            } else {
                if (Files.exists(resultJson)) {
                    logger.warn("File '" + resultJson + "' seems corrupted.");
                } else {
                    logger.warn("Could not find file '" + resultJson + "'.");
                }
            }
        }

        String status = batchExecutor.getStatus(job.getId());
        if (!StringUtils.isEmpty(status) && !status.equals(Enums.ExecutionStatus.UNKNOWN)) {
            return new Enums.ExecutionStatus(status);
        } else {
            Path tmpOutdirPath = Paths.get(job.getOutDir().getUri());
            // Check if the error file is present
            Path errorLog = tmpOutdirPath.resolve(getErrorLogFileName(job));

            if (Files.exists(errorLog)) {
                // FIXME: This may not be true. There is a delay between job starts (i.e. error log appears) and
                //  the analysis result creation

                // There must be some command line error. The job started running but did not finish well, otherwise we would find the
                // analysis-result.yml file
                return new Enums.ExecutionStatus(Enums.ExecutionStatus.ERROR, "Command line error");
            } else {
                return new Enums.ExecutionStatus(Enums.ExecutionStatus.QUEUED);
            }
        }

    }

    private Path getAnalysisResultPath(Job job) {
        Path resultJson = null;
        try (Stream<Path> stream = Files.list(Paths.get(job.getOutDir().getUri()))) {
            resultJson = stream
                    .filter(path -> {
                        String str = path.toString();
                        return str.endsWith(ExecutionResultManager.FILE_EXTENSION)
                                && !str.endsWith(ExecutionResultManager.SWAP_FILE_EXTENSION);
                    })
                    .findFirst()
                    .orElse(null);
        } catch (IOException e) {
            logger.warn("Could not find AnalysisResult file", e);
        }
        return resultJson;
    }

    private ExecutionResult readAnalysisResult(Job job) {
        Path resultJson = getAnalysisResultPath(job);
        if (resultJson != null) {
            return readAnalysisResult(resultJson);
        }
        return null;
    }

    private ExecutionResult readAnalysisResult(Path file) {
        if (file == null) {
            return null;
        }
        int attempts = 0;
        int maxAttempts = 3;
        while (attempts < maxAttempts) {
            attempts++;
            try {
                try (InputStream is = new BufferedInputStream(new FileInputStream(file.toFile()))) {
                    return JacksonUtils.getDefaultObjectMapper().readValue(is, ExecutionResult.class);
                }
            } catch (IOException e) {
                if (attempts == maxAttempts) {
                    logger.error("Could not load AnalysisResult file: " + file.toAbsolutePath(), e);
                } else {
                    logger.warn("Could not load AnalysisResult file: " + file.toAbsolutePath()
                            + ". Retry " + attempts + "/" + maxAttempts
                            + ". " + e.getMessage()
                    );
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException interruption) {
                        // Ignore interruption
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }

        return null;
    }

    private int processFinishedJob(Job job, Enums.ExecutionStatus status) {
        logger.info("[{}] - Processing finished job with status {}", job.getId(), status.getName());

        Path outDirUri = Paths.get(job.getOutDir().getUri());
        Path analysisResultPath = getAnalysisResultPath(job);

        logger.info("[{}] - Registering job results from '{}'", job.getId(), outDirUri);

        ExecutionResult execution;
        if (analysisResultPath != null) {
            execution = readAnalysisResult(analysisResultPath);
            if (execution != null) {
                PrivateJobUpdateParams updateParams = new PrivateJobUpdateParams().setExecution(execution);
                try {
                    jobManager.update(job.getStudy().getId(), job.getId(), updateParams, QueryOptions.empty(), token);
                } catch (CatalogException e) {
                    logger.error("[{}] - Catastrophic error. Could not update job information with final result {}: {}", job.getId(),
                            updateParams.toString(), e.getMessage(), e);
                    return 0;
                }
            }
        } else {
            execution = null;
        }

        List<File> registeredFiles;
        try {
            Predicate<URI> uriPredicate = uri -> !uri.getPath().endsWith(ExecutionResultManager.FILE_EXTENSION)
                    && !uri.getPath().endsWith(ExecutionResultManager.SWAP_FILE_EXTENSION)
                    && !uri.getPath().contains("/scratch_");
            registeredFiles = fileManager.syncUntrackedFiles(job.getStudy().getId(), job.getOutDir().getPath(), uriPredicate, token)
                    .getResults();
        } catch (CatalogException e) {
            logger.error("Could not registered files in Catalog: {}", e.getMessage(), e);
            return 0;
        }

        // Register the job information
        PrivateJobUpdateParams updateParams = new PrivateJobUpdateParams();

        // Process output and log files
        List<File> outputFiles = new LinkedList<>();
        String logFileName = getLogFileName(job);
        String errorLogFileName = getErrorLogFileName(job);
        for (File registeredFile : registeredFiles) {
            if (registeredFile.getName().equals(logFileName)) {
                logger.info("[{}] - stdout file '{}'", job.getId(), registeredFile.getUri().getPath());
                updateParams.setStdout(registeredFile);
            } else if (registeredFile.getName().equals(errorLogFileName)) {
                logger.info("[{}] - stderr file: '{}'", job.getId(), registeredFile.getUri().getPath());
                updateParams.setStderr(registeredFile);
            } else {
                outputFiles.add(registeredFile);
            }
        }
        if (execution != null) {
            for (URI externalFile : execution.getExternalFiles()) {
                Query query = new Query(FileDBAdaptor.QueryParams.URI.key(), externalFile);
                try {
                    OpenCGAResult<File> search = fileManager.search(job.getStudy().getId(), query, FileManager.INCLUDE_FILE_URI_PATH,
                            token);
                    if (search.getNumResults() == 0) {
                        throw new CatalogException("File not found");
                    }
                    outputFiles.add(search.first());
                } catch (CatalogException e) {
                    logger.error("Could not obtain external file {}: {}", externalFile, e.getMessage(), e);
                    return 0;
                }
            }
        }
        updateParams.setOutput(outputFiles);

        updateParams.setInternal(new JobInternal());
        // Check status of analysis result or if there are files that could not be moved to outdir to decide the final result
        if (execution == null) {
            updateParams.getInternal().setStatus(new Enums.ExecutionStatus(Enums.ExecutionStatus.ERROR,
                    "Job could not finish successfully. Missing execution result"));
        } else if (execution.getStatus().getName().equals(Status.Type.ERROR)) {
            updateParams.getInternal().setStatus(new Enums.ExecutionStatus(Enums.ExecutionStatus.ERROR,
                    "Job could not finish successfully"));
        } else {
            switch (status.getName()) {
                case Enums.ExecutionStatus.DONE:
                case Enums.ExecutionStatus.READY:
                    updateParams.getInternal().setStatus(new Enums.ExecutionStatus(Enums.ExecutionStatus.DONE));
                    break;
                case Enums.ExecutionStatus.ABORTED:
                    updateParams.getInternal().setStatus(new Enums.ExecutionStatus(Enums.ExecutionStatus.ERROR, "Job aborted!"));
                    break;
                case Enums.ExecutionStatus.ERROR:
                default:
                    updateParams.getInternal().setStatus(new Enums.ExecutionStatus(Enums.ExecutionStatus.ERROR,
                            "Job could not finish successfully"));
                    break;
            }
        }

        logger.info("[{}] - Updating job information", job.getId());
        // We update the job information
        try {
            jobManager.update(job.getStudy().getId(), job.getId(), updateParams, QueryOptions.empty(), token);
        } catch (CatalogException e) {
            logger.error("[{}] - Catastrophic error. Could not update job information with final result {}: {}", job.getId(),
                    updateParams.toString(), e.getMessage(), e);
            return 0;
        }

        job.getInternal().setStatus(updateParams.getInternal().getStatus());
        notifyStatusChange(job);

        return 1;
    }

    private void notifyStatusChange(Job job) {
        if (job.getInternal().getWebhook().getUrl() != null) {
            executor.submit(() -> {
                try {
                    sendWebhookNotification(job, job.getInternal().getWebhook().getUrl());
                } catch (URISyntaxException | CatalogException | CloneNotSupportedException e) {
                    logger.warn("Could not store notification status: {}", e.getMessage(), e);
                }
            });
        }
    }

    private void sendWebhookNotification(Job job, URL url) throws URISyntaxException, CatalogException, CloneNotSupportedException {
        JobInternal jobInternal = new JobInternal(null, job.getInternal().getWebhook().clone(), null);
        PrivateJobUpdateParams updateParams = new PrivateJobUpdateParams()
                .setInternal(jobInternal);

        Map<String, Object> actionMap = new HashMap<>();
        actionMap.put(JobDBAdaptor.QueryParams.INTERNAL_EVENTS.key(), ParamUtils.UpdateAction.ADD.name());
        QueryOptions options = new QueryOptions(Constants.ACTIONS, actionMap);

        Client client = ClientBuilder.newClient();
        Response post;
        try {
            post = client
                    .target(url.toURI())
                    .request(MediaType.APPLICATION_JSON)
                    .property(ClientProperties.CONNECT_TIMEOUT, 1000)
                    .property(ClientProperties.READ_TIMEOUT, 5000)
                    .post(Entity.json(job));
        } catch (ProcessingException e) {
            jobInternal.getWebhook().getStatus().put(job.getInternal().getStatus().getName(), JobInternalWebhook.Status.ERROR);
            jobInternal.setEvents(Collections.singletonList(new Event(Event.Type.ERROR, "Could not notify through webhook. "
                    + e.getMessage())));

            jobManager.update(job.getStudy().getId(), job.getId(), updateParams, options, token);

            return;
        }
        if (post.getStatus() == HttpStatus.SC_OK) {
            jobInternal.getWebhook().getStatus().put(job.getInternal().getStatus().getName(), JobInternalWebhook.Status.SUCCESS);
        } else {
            jobInternal.getWebhook().getStatus().put(job.getInternal().getStatus().getName(), JobInternalWebhook.Status.ERROR);
            jobInternal.setEvents(Collections.singletonList(new Event(Event.Type.ERROR, "Could not notify through webhook. HTTP response "
                    + "code: " + post.getStatus())));
        }

        jobManager.update(job.getStudy().getId(), job.getId(), updateParams, options, token);
    }

    private String getErrorLogFileName(Job job) {
        return job.getId() + ".err";
    }

    private String getLogFileName(Job job) {
        // WARNING: If we change the way we name log files, we will also need to change it in the "log" method from the JobManager !!
        return job.getId() + ".log";
    }

}


I hold no brief for the White Sox as currently constituted and, save for a few personal and historical attachments, have no surpassing interest in the White Sox any more than I do the Angels, whom we just saw, or the Athletics, whom we are about to see. The White Sox rushing to Flushing is not something that would necessarily quicken my pulse, but for cryin' out loud, how is it they are the only American League team in now fifteen seasons of Interleague competition to have never come to Shea Stadium or Citi Field?
Where in the world are the Chicago White Sox? And why in the name of Charley Smith, Lance Johnson and Robin Ventura are they afraid of playing us in New York?
They beat us two of three at what was then called new Comiskey Park in 2002, the only time we've played them outside of Spring Training — which stopped being convenient when the Mets moved to St. Lucie in 1988 and ceased to be possible once the Sox abandoned Sarasota for Arizona in 1998. The Mets have gone longer without an official game versus the White Sox than they have against any opponent, active or otherwise, from either league. Defunctitude isn't a dealbreaker; the Mets have played the Montreal Expos more recently than they've played the Chicago White Sox (though the Sox still have the Milwaukee Braves beat by 37 years).
Barring acts of Bud, you can add 3X to Oakland, Texas, Detroit and the other New York team as of two weeks from now. Thus, by the Fourth of July, we will have seen the Mets compete against representatives of 13 junior circuit clubs 156 times since June 12, 2002 — nearly an entire season's worth of games — and we'll have had no dates with the White Sox. Furthermore, just prior to that June 10-12, 2002, series at the future U.S. Cellular Field, we had a three-game set in Cleveland. For the five seasons prior, we regularly saw every non-New York club from the A.L. East for three games a year, alternating annually home and away.
Throw in 27 regular-season Subway Series dates and that's 91 consecutive Interleague games of the non-White Sox variety for the Mets once the sacred boundaries between N.L. and A.L. were blurred forever in 1997. Thus, out of 250 Interleague games in toto, we've seen the White Sox less than we've seen anybody, and we've seen the White Sox here not at all.
I don't care so much that forces have aligned to allow the White Sox to avoid us and our ballpark(s) as I want to understand why. I understood when it was East vs. East through 2001. I understood why that was considered repetitious after a while. I understood that these things would cycle and even out from there.
Because we are forced/fortunate to play six NY-NY games every year, I realized there would be an occasional inequity, but I figured it would all come out in the wash. In 2003 and again from 2005 through 2009, we were one of those National League teams that landed an extra National League series via whatever math deprived us of getting, say, an entire American League West slate. But eventually those opponents we missed would materialize in our midst, which I thought was kind of the idea of Interleague play. Fans who didn't get to see teams from the other league as a matter of course would, sooner or later, get to see every team from the other league once in a while. Or once.
I'm still waiting to see the White Sox.
But never the White Sox. Never at Shea. Still never at Citi. And not at the Cell since before it became the Cell. The White Sox have a two-year lead on the Royals in the American League race to avoid the Mets and are at least four years ahead of everybody else from over there. The Royals, whom we haven't played since 2004, at least had the decency to make an appearance at Shea in 2002. It was so long ago that they had Carlos Beltran and we had Tony Tarasco, but they showed up. The Blue Jays haven't been by since 2001, but they were the perfect guests for three series: nine games with us, nine losses for them. The A's (putting aside the events of October 1973) remained at large long after the rest of the West touched down next to LaGuardia, but they squeezed themselves onto our home schedule in 2007 even though we weren't playing the Angels, Rangers or Mariners that year. I considered that very considerate of them.
Everybody and everything has been done in terms of Interleague. Done to death. So done to death that some powers that be think they can now parlay their blasphemic blurring into some sort of ridiculous realignment. But they forgot one little detail. They forgot to send the White Sox to play the Mets where the Mets play. I'd go to see the White Sox at Citi Field because I go to see every American League team play at Shea or Citi at least once, even in the rain. Whether the American League team gets to play the Mets is up to the heavens, but I'll show up.
After the White Sox visit the Mets, kill the whole Interleague thing, Subway Series and all. I used to modestly defend it. I have no use for it any longer. I just watched the Mets play the Angels for three days and felt nothing — and the Angels are the closest thing to a favorite American League team I've had in the last decade. If the Angels can't move my needle, the A's, the Rangers and the Tigers aren't going to, either…and the possibility of some local A.L. opponent's 3,000th hit occurring during a Mets game definitely won't. Hand Interleague its hat, call it a cab and tell it we'll remember it intermittently fondly as it's driven away. We don't have to hurt its feelings, but we do need to get rid of it already.
But first get the White Sox here. Or, failing that, tell me why they won't come.
10 comments to Bring On the White Sox!
I was just waiting for them to play the Royals again. I remember going to KC in 2004 and watching Glavine pitch a gem in Kauffman Stadium… and Piazza hitting a bomb to center that Carlos Beltran (then on the Royals) crashed into the wall, and got hurt chasing.
Perhaps the only bigger chunk of ridiculousness is that Selig's scheduler STILL hasn't managed to bring the Dodgers back to the Bronx for a regular season game.
The Wilpons would probably offer to host one or two of them so Dem Bums would feel more at home.
The hat that they use to pick these games out of just won't give it up. I too used to insist on seeing every AL team come to Shea. Of these, the only two I've missed are the Mariners and, of course, the White Sox. The only series that was actually a fun rivalry was playing the Red Sox. Why they don't alternate divisions like the NFL does–East plays Central one year, East plays West the next, etc.–is kind of annoying. Those years the Mets were losing out of the playoffs by one game, you think the inequity between interleague opponents might have made that lil bit o' difference?
My understanding is that in 2013 we will move from an interleague to an intersport schedule with an inaugural Mets/Dolphins game hosted in Sao Paulo, Brazil.
Come to think of it, I don't recall the Dolphins ever coming to Citifield. In truth, I don't care if the Mets are hosting the Jersey Jackals for 3 as long as they can sweep a series and get over that darn .500 mark to stay.
Hasn't it been 10 years now since the Red Sox last visited the Mets — the "soft open" of CitiField before the start of the 2009 regular season notwithstanding?
Now that you mention it, yes it has been ten years. OTOH, we visited the Orioles in consecutive years, 2009 and 2010. At least it was an easy trip for Mets fans.
Does anybody really care about this homestand? The Angels and the A's, really?
Sounds very compelling doesn't it. This could be the "Next Big Thing" in baseball….. Cross Country Rivalries!!!….Thats Brilliant!
Wake me up when we get back to playing real teams!
I am heading out to the College World Series for the final week!
Was at the Mariners game where King Felix hit a granny off Santana. Feh.
Your longing for a White Sox visit just shows how much you want to see Lastings Millege back in Flushing.


text:
The history of events: ideology and historiography
Gold, John Robert and Gold, Margaret (2020) The history of events: ideology and historiography. In: The Routledge Handbook of Events (2nd edition). Routledge, Abingdon. ISBN 9780367236489
Gold and Gold History of Events 2019 final.pdf - Accepted Version
Official URL: https://www.routledge.com/The-Routledge-Handbook-o...
This chapter explores the contribution that explicit analysis of historical writings can make to the study of events. In particular, it explores three related propositions. The first concerns "narrative", understood here as a structured account, rendered in textual form, of a sequence of events that occurred in the past. The second proposition continues the story-telling theme, noting that it is important to recognise the ways that events can be used to represent specific causes and to understand the consequences of doing so. In this regard, we point to the importance of the rich and multifaceted concept of "representation". The third proposition concerns "narration", or the way in which the story is told. Here, we argue that understanding of the history of events would benefit from more explicit recognition of the multiple ways in which that history has been, and could be, written. Case studies of the Salzburg Festival, the visit of George IV to Edinburgh in 1822, and the history of the modern Olympic Games are used to illustrate these propositions in turn.
events, cities, festivals, historiography, representation, narrative,
300 Social sciences > 390 Customs, etiquette & folklore
700 The arts; fine & decorative arts > 790 Recreational & performing arts
900 History & geography
Guildhall School of Business and Law
Margaret Gold

redpajama_set_name: RedPajamaCommonCrawl


The presentation is intended for the practicing business analysts who are striving to increase their performance, as well as for the managers who are struggling with the problem of improving business analysis practices in the organizations. It explains an approach to assessing and improving the performance of business analysts, using the methods of Lean Manufacturing. The explanations are accompanied by examples from the world practice and personal experience of the author.
Бережливый бизнес-аналитик: как устранять 8 видов потерь from Vlad Orlikov on Vimeo.


Forced sequel to "Get Shorty" that lacks the smoothness and finesse of the original and seems to try both too much and too little at the same time. It is not nearly as fun now and the setup seems more interested in bringing in as many celeb cameo appearances that is possible. Worst mistake though is a dance sequence that try to make the lousiest Black Eyed Peas song hip when it is as soulless and dead as this film.
50/50 I felt bad because Vince Vaughn and The Rock both give the best performances of their careers and it is lost on a script that doesn't go anywhere and the spontaneously break into one actor whose character is scarcely established lip syncing REALLY badly, and whichever recording she used wasn't great. There are several of those scenes through the film which on repeat views I just want to put on something else.
This was a very disappointing sequel but it still delivered some pretty good moments.
Favourite movie off al time!


text:
Scientists spot beam of black hole energy as bright as 1,000 trillion suns pointed at Earth
Leave a Comment / Physics / By admin
Image: Carl Knox – OzGrav, ARC Center of Excellence for Gravitational Wave Discovery, Swinburne University of Technology
On a clear night last February, a sophisticated astronomical camera perched on top of a California mountain opened its eyes to the sky after being turned off for several days due to problems. The timing turned out to be impeccable, as the camera, part of the Zwicky Transient Facility (ZTF), immediately spotted a dazzling eruption of light and energy in the distant universe that was unlike anything that had been seen before.
When ZTF astronomers logged into their Slack work the next day, they shared their joy at the brilliant discovery, as well as their bewilderment as to what could have triggered this flash that shone with the light of 1,000 trillion suns.
"My first message on Slack was, 'Are we starting again with a bang?'" Igor Andreoni, a University of Maryland astronomer who works at ZTF, said in a call with Motherboard that also included his colleague. Michael Coughlin, an astronomer at the University of Minnesota. "Engineers worked tirelessly, including weekends, day and night, to get the camera operational again. And literally the first night it was operational, there was this detection.
Now, a team co-led by Andreoni and Coughlin reports that the mysterious flash was actually a powerful beam of energy – an immensely energetic jet of lightning-fast particles pointed at Earth, powered by a black hole devouring a star at many billions of light-years from our planet. This type of radiant cosmic phenomenon is called a tidal disturbance event (TDE).
The researchers concluded that this unusual explosion, named AT2022cmc, is "the most distant jet TDE discovered to date and the only one for which it has been possible to observe a complex optical light curve which passes from a red component fast to a blue plateau," according to a study published Wednesday in Nature. An additional study published in natural astronomy at the same time presented a detailed model of the birth of this jet.
The surprise discovery "reaffirms the need to have truly open research on these growing astronomical datasets that we have," Coughlin said in the call, noting that he and Andreoni were looking for a completely different type of astronomical event. in their ZTF. investigation. "It's a great example in astronomy of a willingness to think outside the box for exciting things."
"It's a source that we didn't really think about until it was discovered, because it's so rare," Andreoni noted. "It's not quite the first thing you think of – an event that is detected once every 10 years, if that – and has never been discovered before in the optics [wavelengths]. It is unprecedented.
Although black holes are often invisible to light-based astronomy, they can paradoxically produce some of the brightest light shows in the universe if they feed on frenzy. Stars that wander too close to a black hole can be torn apart by the intense tidal forces in these extreme environments. By consuming its starry meal, a black hole can emit beams of "relativistic" particles, that is to say that they move at a speed close to the speed of light.
Jets that are aimed directly at Earth can be seen over great distances, although none have been as far away as AT2022cmc. Tracking of the event captured by the European Southern Observatory's Very Large Telescope revealed that this particular jet was born when the universe was just a few billion years old. In addition to breaking the distance record for TDEs, the presence of so much visible light from AT2022cmc opens a new window into the mysterious mechanisms that power these jets.
"It already tells us a story about the environment surrounding this very massive black hole," Andreoni said. "It also tells us about the time scale of the formation of the jet, relative to the disk of material around the black hole that is building up and continuing to pump energy that we can observe."
"The other positive effect is that, of course, it is very difficult to estimate how often a [jet] childbirth happens," Coughlin added. "People had made these estimates based on x-ray detections in the past, but the Zwicky transient facility has been running for about four years now, and we've seen one [in optical light]. This actually puts quite strong constraints on how often they can occur.
With these new revelations in hand, astronomers are better equipped to search for more of these mind-boggling events across the universe, which will help unlock their explosive secrets.
"We're trying to contribute to a cohesive picture of all of these tidal events that we observe, with and without jets," Andreoni said. "It's an important question because once you find the connection, you can get a much better idea of what's really going on when a star is disrupted by a black hole."
"We just need to observe deeper and deeper," he concluded. "I think we'll get much closer to why they're so rare and the astrophysics of these events."
#Scientists #spot #beam #black #hole #energy #bright #trillion #suns #pointed #Earth
Historic Moon Mission Ends With Orion Capsule Splashdown | CNN
Healthy soil for more nutritious food and a healthier planet
Genetic resources emerge as a defining issue at COP15
Japanese company's lander heads for the moon with a rover from the United Arab Emirates
Snowflake zinc crystals, the strongest material and "hellish planet"
Copyright © 2023 madeira oceanos
cookielawinfo-checkbox-functional
cookielawinfo-checkbox-others


In addition to obtaining useful information by this internet site, couples can use interactive self-help applications, scheduled stay chats with expert therapists or certified professionals and substantially extra.
This internet site is focused to serving to couples make improvements to their partnership by giving information, workshops and the exclusive, cutting edge, interactive "Couple's Workstation".
Get on-line assistance, a adore oracle, self-help content, partnership coaching, couples counseling, relationship counseling, couples retreats, relationship retreats, and applications for partnership improvement.
Get partnership assistance, partnership help and adore assistance listed here.
This web page features you assistance, applications and help that can assist you to produce a satisfying adore everyday living and a excellent partnership.
A excellent partnership is just one of the very best matters everyday living can supply.
Asking yourself how can I help save my relationship? Repair your relationship troubles today with our professional partnership assistance and help.
The Magic of Making Up is our featured products because I have experienced so numerous email messages occur in telling me how substantially this products has helped people today in so numerous various cases.
You can see how to conveniently make a partnership get the job done and have a far better comprehension of the intricate character of that magical emotion we get in touch with.
Discover alternatives to dating, adore and partnership issues.
Are you looking for some partnership assistance because you might be sick and exhausted of your passionate associations remaining a position of discomfort?
Number of associations are no cost from friction our partnership assistance is created to help you by hard phases in your partnership.
With our relationship advice, you can be on your way to acquiring a fruitful and healthier partnership after once more.


text:
Arts & Entertainment » Choice Events
THEATER | 'Boom'
by Leah Stacy
From the pen of young playwright Peter Sinn Nachtrieb comes "Boom," a circa-2008 apocalyptic comedy about a marine biologist and a journalism student who meet via the internet for a hookup — advertised as "sex to change the course of the world" — for the sake of the biologist's research. Local theater company Out of Pocket Productions has taken on this rarely-produced (but much raved about in New York City) play, which is directed by Stephanie Roosa and features Skylar Shaw, Lauren MacDonough, and Barbara Lobb in the three-person cast.
"Boom" continues Thursday, October 31, through Saturday, November 2, at 7:30 p.m. MuCCC, 142 Atlantic Avenue. Tickets are $13 for students and seniors, $18 general admission and can be purchased at muccc.org.


text:
Q: What does "arg inf" mean? I noticed this term on this post.
But the term arg inf is not clearly defined.

A: It refers to the argument of the function such that the function attains its infimum (or minimum). For example, let $f(x) = x^2-x$. Then $\arg\inf f(x) = {1\over 2}$ because $f(x)$ attains its minimum at $x= {1\over 2}$.

redpajama_set_name: RedPajamaStackExchange


Meeting the Business Process Outsourcing needs of governmental and commercial organizations requires agile and highly scalable customer support technologies.
In today's data intensive world, speed, accuracy, and a passion for precise program management are critical. To deliver the highest quality BPO services Solix 360 employs next generation, cloud-based IT technologies. From telecommunications to healthcare and regional utilities, the Solix 360 platform is proven to support a wide range of detail intensive claims and eligibility processing, data recording, and customer care functions.
Our advanced technology platform allows Solix to quickly bring workflow automation solutions for enrollment, eligibility determination, and case management to market across a wide range of industries. Our agile software development processes enable flexible solution design and faster time-to-market.
We understand the needs of large-scale, consumer-focused programs. Our diligence and detail focus is why we are a leading provider of outsourced services to a broad range of organizations that require precise program execution and white glove customer care. Solix works hard to support the operational and business challenges of commercial and government operated programs targeting families across the nation. We understand the delicate balance that must be maintained between customer satisfaction and the need to manage costs and program efficiency.
Our Solix 360 platform enables us to deliver the highest quality customer support while integrating critical consumer-specific data in a highly scalable, flexible and secure technological environment.
We've invested significantly to make the Solix 360 technology platform highly adaptive to complex business processes. The platform enables rapid prototyping and implementation of client-specific business rules while reducing program time-to-market.
We understand the importance of accurate eligibility determination, compliance management, and data security inherent in complex government and commercial based programs. Solix 360 provides the system, templates and data integration to enable our Contact Center staff to process and case manage complex consumer programs. Our technology makes real time data capture and decisioning possible using customized application review templates.
Solix 360 workflow, data, and critical program processes can be customized to client specific business needs and maintained according to client end user/customer requirements. Solix 360 technology supports client specific customer data intake and inbound customer support requirements.
For 20 years Solix has supported some high profile, government and commercial programs. Let Solix show you how we can help you implement complex, large-scale support programs while delivering improved financial metrics for your organization. When efficiency, attention to detail, and customer satisfaction are key…count on Solix.


text:
This document is TRUE General Terms & Conditions enforceable for Issuers, Recipients and Site Users. The Issuers, Recipients and Site Users who wish to subscribe to TRUE's Services must agree to this agreement.
The Issuer agrees to respect the confidentiality of the project, the services, the Internet site, and the computer system of TRUE, and to respect the intellectual property of TRUE. Issuers promise not to perform any activity that competes with the TRUE site and accepts that TRUE may interrupt, suspend or modify the services accessible on this site at such time that competing activities are suspected.
Article 1. Definitions and identification of the various participants
TRUE is the provider of the Services defined below, operating from the TRUE Internet platform made up of a computer system and databases specifically designed for the Services, and accessible at the URL addresses: www.TRUEoriginal.com, www.TrueCrt.com, https://ad.truecrt.com, and , https://dash.trueoriginal.com (the Site).
The company, True Value Software AB, is registered at Valhallavägen 168, 11524 Stockholm, Sweden. Email: support@trueoriginal. com.
The "Issuer" is any institution, organization, company, or person issuing digital documents, such as certificates, diplomas, memberships, recognitions, accreditations, awards, etc, referred to as Documents (previously Crt's) in the TRUE system, to people, organizations or companies which the "Issuer" deem worthy of the said accreditation.
The "Recipient" is any person, organization or company which has been given /sent / issued a digital accreditation (Document or Crt). A Recipient may have taken a course at an Institution, reached a milestone or simply deemed to have done something worthy of recognition from the Issuer.
The "Viewer or User" is any natural or juristic person interested, on his own behalf or on behalf of a third party, in the profile of a Recipient for any purpose. It may be (without the following being exhaustive) a hiring agency, a human resources department of a company, a public institution, a representative of a juristic person or a natural person.
"The Parties" mean, without distinction, every party registering on the TRUE site or participating in the Services offered by TRUE.
The "Accreditation", "Document", or "Crt" is any certificate, award, diploma or recognition given out / sent / issued by an Issuer. This Document may certify a level of education, a milestone, knowledge, or other achievement reached by a Recipient. The "Document" is also a digital representation given by the Issuer to the Recipient, using TRUE's digital 'Document' Service, in electronic form with a secure mechanism guaranteeing, by encryption, the source of the issuer of the Accreditation (the Issuer) and the unchangeability of its content after it beings Issued and accepted by the Recipient.
The "Site" means the TRUE Internet site, primarily accessible at the address www.trueoriginal.com (formerly www.truecrt.com), ad.truecrt.com and dash.trueoriginal.com, but also on the subpages of the Issuer that displays the Document's issued by the Issuer (example: diploma.Issuer.com, or certificate.Issuerwebsite.com).
An "Achievement" is the reason an Issuer decides to give a Recipient a Document (formerly Crt).
Article 2. Application of the General Terms & Conditions
This document is a public representation of the General Terms & Conditions. The Terms and Conditions is enforceable for the Issuer and the Recipients who have registered an account on TRUE, contracted TRUE to issue Document's and received a Document trough the TRUE website, or received a Document from the Service.
In the event of the adaptation, amendment or establishment of a new version of the present General Terms & Conditions, the Parties shall be informed of it and shall have access to the new applicable text.
If a Party does not accept the general conditions, it may not access the Site for as long as it has not accepted them.
The new General Terms & Conditions shall apply by default, until the acceptance or refusal to accept them.
Article 3. Definition of the Service offered by TRUE to the Issuer
TRUE provides the "Digital Accreditations Service"-solution, enabling Issuers to create unique Electronic Documents, to publish them on a subdomain of the issuers choice, and send them to the Recipients email, as well as store them in a secure manner.
TRUE guarantees the identity of the Issuer giving out the Electronic Accreditation and the unchangeability of the Electronic Accreditation / Document, after it has been approved/accepted by Recipients. TRUE does not guarantee the validity of any of the content in the Electronic Accreditation / Document, just that it cannot be changed after issued by the Issuer. The Issuer is solely responsible for making sure the data in the Electronic Accreditations / Document are correct when issued and published on the web. The TRUE icon/logo will be present on each Document as proof of the document content uniqueness and security.
TRUE will always strive to only work with respected organisations and companies, ie Issuers who have a high standard of conduct and never set out to defraud or fool Recipients or Viewers. If you suspect an Issuer to use TRUE's plattform in a shady fashion please contact us immediately.
Article 4. Definition of the Service offered by TRUE to the Issuer and Recipient
The Internet platform offers to Recipients a page with a specially designed interface for the presentation of Electronic Accreditations / Documents, which may then be made publicly accessible by anyone.
The Electronic Accreditation / Document is generated on the initiative of the Issuer.
TRUE gives the Recipient and Viewers the possibility to access the Electronic Accreditation in the form of a pdf document, hyperlink or QR code. The Electronic Accreditation / Document can also be posted on social networks as well as downloaded as a pdf.
Access to the Electronic Accreditation / Document enable anyone to assess the veracity of the information provided by the Issuer.
The Issuer is fully responsible for all information uploaded or accessed by TRUE. Issuer need to fully cooperate with GDPR rules and regulations.
Article 5. Registration of the Issuer
In order to benefit from the Services of TRUE, the Issuer must register on the Site using an online form or communicate the intent to Issue Documents with a TRUE representative via Email, who at that time will input the Issuer as a user in the TRUE system and the Issuer will be able to access the site.
Article 6. Registration of the Recipients
In most cases, the Recipient who wishes to receive their Electronic Accreditation / Document first needs to accept GDPR rules as well as Terms & Conditions on the Site and allow access to the personal data entered by the Issuer (such as last name, middle name, firstname, email, phone, title, company, profile picture, personal id number). In the event that a Recipient is a company the Issuer can choose to skip the process of requesting the acceptance of GDPR rules. It is always the responsibility of the Issuer to adhere to GDPR and other data compliance regulations in their country or region.
Article 7. Publication of the Electronic Accreditation / Documents
The Electronic Accreditation / Document may be made accessible freely and shown either on Issuers subdomain or on TRUE Subdomain.
Issuer takes full responsibility of the validity, the data and the personal info of the Recipient
Anyone can send the URL link or QR Code in an e-mail, unless specifically requested by Issuer in writing, and/or locked by the Recipient with a personal passcode.
Anyone can insert the URL link or QR Code on a web platform or Profile, unless specifically requested by Issuer in writing, and /or locked by the Recipient with a personal passcode.
Anyone may save the Electronic Accreditations in PDF format, print them, send them in emails or download them independently of the TRUE platform, unless specifically requested otherwise by Issuer in writing and /or locked by the Recipient with a personal passcode.
Anyone can post the Document on social networks such as Facebook, Linkedin, Instagram, and others, unless specifically requested by Issuer in writing, /or locked by the Recipient with a personal passcode.
The Documents (formerly Crt's) may in some instances have an expiration date, set by the issuer.
Article 8. Conditions of use of the TRUE Service
The Service is free for the Recipients as well as for Users / Viewers.
Article 9. Responsibility of TRUE
TRUE will try to ensure that Issuers use the service for good, and are truthful with the data and claims of Issued Documents, but are not responsible for such claims or data posted on Documents.
TRUE expressly declines responsibility in the event of the non-observance of the obligations of the parties or in the event of damage that is directly or indirectly imputable to them. If applicable, the Party in the wrong or who is liable shall indemnify TRUE against any judgement pronounced against it.
TRUE is not responsible either for the erroneous nature of certain data communicated by the parties on the Site.
TRUE is not responsible for the use that third parties make of the data communicated by the Parties on the Site.
TRUE has no general obligation to monitor the information that it sends or stores, nor any general obligation to actively search for facts or circumstances revealing illicit activities.
In the event of the publication of illicit content on the Site, TRUE may only be held liable in the following cases:
If it actually knew of the illicit activity or information,
and if it did not respond promptly, as soon as it knew of all the required details, to remove the illicit information or to make access to it impossible.
TRUE may be asked to communicate to the competent judicial or administrative authorities the information enabling them to identify the Parties in the event of illicit activities being performed on the Site, with observance of the applicable procedures.
TRUE does not guarantee that the Services are accessible if the Internet Service provider of the Parties defaults. TRUE is not responsible for the non-operation, impossibility of access or poor conditions of use due to unsuitable equipment, malfunctions attributable to the Internet Service provider of the Parties, congestion on the Internet, malfunctions of the host and the provider of the TRUE Services, or for any other reason that is not due to wrongdoing directly imputable to TRUE. TRUE declines responsibility in the event of loss of data due to the poor functioning of the Site, backup data should be maintained by Issuer.
TRUE may temporarily interrupt access to the Site and/or the Services for reasons of maintenance, updates, technical improvements or development of the content or its presentation.
TRUE Issuers may include hypertext links to other Websites or other Internet sources. However, TRUE is not able to verify or control these external sources and may not be held responsible for the provision of these sites or sources, nor their content, publicity, products or services offered. The parties consult them at their own risk and under their own responsibility.
TRUE does not guarantee the continuity of its Services and may need ot move content to Issuer in the event of business failure, or going out of business.
Article 10. Intellectual property
The Issuer is fully responsible for all images, logos, data, organizational or personal data of the issued Accreditations / Documents.
The interface, logos, graphics, photographs, animations, videos and text contained on the Site are the intellectual property of TRUE or its partners, Issuers and / or Recipients, and may not be reproduced, used or represented without its express authorisation. The same applies to the TRUE database and its content, and to the source codes of the Site as well as any software enabling its operation.
The user rights for these elements, granted to the parties by TRUE, are limited to access, downloading, printing and reproduction on any media and the use of these documents for private or professional purposes, while registered with TRUE only and for the purpose of using the Services.
Except in the cases expressly provided by these presents, the Parties are prohibited from changing, copying, reproducing, downloading, distributing, sending, decompiling and compiling, exploiting commercially and distributing in any manner, the pages or parts of pages of the Site or the computer codes of the elements making up the underlying software of the Site.
TRUE and its logo are a trademark of TRUE.
Article 11. General clauses
The present General Terms & Conditions constitute a contract governing the relations between the Parties and TRUE.
If certain provisions of these presents turn out to be null and void, the other non-invalidated provisions shall continue to apply.
Without prejudice to article 12, TRUE may, at any time and without notice, amend the present General Conditions, at which time the Issuers will be made aware view email of such changes. The Parties shall ensure that they are always familiar with the latest version.
Article 12. Applicable law and jurisdiction
The present General Conditions are subject to Swedish law.
In the event of a dispute relating to these General Conditions, the use of the Site or the Services, only the courts of the legal district of Sweden shall have jurisdiction.
Last updated: Sept 17, 2021
This Cookies Policy explains what Cookies are and how We use them. You should read this policy so You can understand what type of cookies We use, or the information We collect using Cookies and how that information is used.
Cookies do not typically contain any information that personally identifies a user, but personal information that we store about You may be linked to the information stored in and obtained from Cookies. For further information on how We use, store and keep your personal data secure, see our Privacy Policy.
We do not store sensitive personal information, such as mailing addresses, account passwords, etc. in the Cookies We use.
The words of which the initial letter is capitalised have meanings defined under the following conditions.
For the purposes of this Cookies Policy:
– Company (referred to as either "the Company", "We", "Us" or "Our" in this Cookies Policy) refers to TrueCrt and True Value Software AB.
– You means the individual accessing or using the Website, or a company, or any legal entity on behalf of which such individual is accessing or using the Website, as applicable.
– Cookies means small files that are placed on Your computer, mobile device or any other device by a website, containing details of your browsing history on that website among its many uses.
– Website accessible from https://www.TrueOriginal.com and the subpages where Documents are shown.
The use of the Cookies
Type of Cookies We Use
– Necessary / Essential Cookies
– Cookies Policy / Notice Acceptance Cookies
– Functionality Cookies
– Tracking and Performance Cookies
Administered by:Third-Parties
Purpose: These Cookies are used to track information about traffic to the Website and how users use the Website. The information gathered via these Cookies may directly or indirectly identify you as an individual visitor. This is because the information collected is typically linked to a pseudonymous identifier associated with the device you use to access the Website. We may also use these Cookies to test new advertisements, pages, features or new functionality of the Website to see how our users react to them.
– Targeting and Advertising Cookies
– Social Media Cookies
Purpose: In addition to Our own Cookies, We may also use various third parties Cookies to report usage statistics of the Website, deliver advertisements on and through the Website, and so on. These Cookies may be used when You share information using a social media networking websites such as Facebook, Instagram, Twitter or Google+.
Your Choices Regarding Cookies
If You prefer to avoid the use of Cookies on the Website, first You must disable the use of Cookies in your browser and then delete the Cookies saved in your browser associated with this website. You may use this option for preventing the use of Cookies at any time.
If You do not accept Our Cookies, You may experience some inconvenience in your use of the Website and some features may not function properly.
– For the Chrome web browser, please visit this page from Google: https://support.google.com/accounts/answer/32050
– For the Internet Explorer web browser, please visit this page from Microsoft: http://support.microsoft.com/kb/278835
– For the Firefox web browser, please visit this page from Mozilla: https://support.mozilla.org/en-US/kb/delete-cookies-remove-info-websites-stored
– For the Safari web browser, please visit this page from Apple: https://support.apple.com/guide/safari/manage-cookies-and-website-data-sfri11471/mac
More Information about Cookies
You can learn more about Cookies at the following third-party websites:
– Network Advertising Initiative: http://www.networkadvertising.org/
If you have any questions about this Cookies Policy, feel free to reach out via webform or email.
TRUE DIGITAL Documents
Accreditations • Authentications • Awards • Badges • Certifications • Credentials • Diplomas • Degrees • Endorsements • Honors • Licenses • Memberships • Proofs • Transcripts • Verifications • Vouchers
Crafted with True Value Software AB


One of the most beautiful things about the bride of Christ is that she doesn't look like me. She is a beautiful unity woven together by God from a beautiful diversity of people who do not look the same.
I do not imagine Jesus as a blond-haired, blue-eyed Scandinavian male model. I envision him in my mind as a dark-skinned peasant with dirt under his fingernails and hands worn with callouses from manual labor. Whatever Jesus looked like during his time on earth, I know for sure he did not look like me.
I've been to Mexico and met believers with whom I could barely communicate an intelligible sentence (I was operating at high school level "me gustan tacos" Spanish at that time). And yet, despite the language and culture barrier, I felt a closeness stronger than blood because of our common faith in Christ.
I've been to Africa where the skin of brothers and sisters in Christ was so dark that I noticed how pale I am. As we shared meals together and gathered in a circle to pray and sing, I felt like I saw the sights and heard the sounds of heaven.
The hatred on display in Charlottesville is a picture of what hell looks like. It is anti-Christ, anti-church, and anti-human. You can't read the New Testament without missing the deep themes of reconciliation, Gods' love in Christ for all people, and the unity of the body of Christ built with the bricks of many cultures, nations, and languages. The coming together of people once hostile and suspicious of one another into the single family of God is a beautiful thread woven into the Gospels and Epistles.
For through him we both have access in one Spirit to the Father. So then you are no longer strangers and aliens, but you are fellow citizens with the saints and members of the household of God... (Ephesians 2:19-20).
May it be that the church of Jesus Christ boasts all the more of its diversity - louder than the voices of those who boast in hate.


At 7 Hills Church, we know that Easter is all about celebrating the resurrection of Jesus with the ones we love! We know it can be difficult to find a church home that the whole family enjoys, but at 7 Hills our heart is to create a place where everyone you love feels right at home.
Every year, thousands of families just like yours join us for Easter and we believe your family will find their place here with us this year and for many years to come.
In Hills Kidz, the children's ministry of 7 Hills Church, we believe kids deserve a service experience of their own on Easter!
You can expect an age-specific service for your child, live music, live skits, and an Easter egg hunt after each service with 175,000 eggs! This will be an Easter experience they will never forget!
Wondering what to expect? Watch last year's Easter service by clicking the button below!