org.ohdsi.webapi.service.FeasibilityService.java Source code

Java tutorial

Introduction

Here is the source code for org.ohdsi.webapi.service.FeasibilityService.java

Source

/*
 * Copyright 2015 Observational Health Data Sciences and Informatics [OHDSI.org].
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ohdsi.webapi.service;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.apache.commons.lang3.StringUtils;
import org.ohdsi.sql.SqlTranslate;
import org.ohdsi.webapi.feasibility.InclusionRule;
import org.ohdsi.webapi.feasibility.FeasibilityStudy;
import org.ohdsi.webapi.feasibility.PerformFeasibilityTasklet;
import org.ohdsi.webapi.feasibility.StudyGenerationInfo;
import org.ohdsi.webapi.feasibility.FeasibilityReport;
import org.ohdsi.webapi.cohortdefinition.CohortDefinition;
import org.ohdsi.webapi.cohortdefinition.CohortDefinitionRepository;
import org.ohdsi.webapi.TerminateJobStepExceptionHandler;
import org.ohdsi.webapi.cohortdefinition.CohortDefinitionDetails;
import org.ohdsi.webapi.cohortdefinition.CohortExpression;
import org.ohdsi.webapi.cohortdefinition.CohortGenerationInfo;
import org.ohdsi.webapi.cohortdefinition.CriteriaGroup;
import org.ohdsi.webapi.cohortdefinition.ExpressionType;
import org.ohdsi.webapi.feasibility.FeasibilityStudyRepository;
import org.ohdsi.webapi.cohortdefinition.GenerateCohortTasklet;
import org.ohdsi.webapi.GenerationStatus;
import org.ohdsi.webapi.job.JobExecutionResource;
import org.ohdsi.webapi.job.JobTemplate;
import org.ohdsi.webapi.shiro.management.Security;
import org.ohdsi.webapi.source.Source;
import org.ohdsi.webapi.source.SourceDaimon;
import org.ohdsi.webapi.util.SessionUtils;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

/**
 *
 * @author Chris Knoll <cknoll@ohdsi.org>
 */
@Path("/feasibility/")
@Component
public class FeasibilityService extends AbstractDaoService {

    @Autowired
    private CohortDefinitionRepository cohortDefinitionRepository;

    @Autowired
    private FeasibilityStudyRepository feasibilityStudyRepository;

    @Autowired
    private CohortDefinitionService definitionService;

    @Autowired
    private JobBuilderFactory jobBuilders;

    @Autowired
    private StepBuilderFactory stepBuilders;

    @Autowired
    private JobTemplate jobTemplate;

    @Autowired
    private Security security;

    @Context
    ServletContext context;

    private StudyGenerationInfo findStudyGenerationInfoBySourceId(Collection<StudyGenerationInfo> infoList,
            Integer sourceId) {
        for (StudyGenerationInfo info : infoList) {
            if (info.getId().getSourceId() == sourceId) {
                return info;
            }
        }
        return null;
    }

    private CohortGenerationInfo findCohortGenerationInfoBySourceId(Collection<CohortGenerationInfo> infoList,
            Integer sourceId) {
        for (CohortGenerationInfo info : infoList) {
            if (info.getId().getSourceId() == sourceId) {
                return info;
            }
        }
        return null;
    }

    public static class FeasibilityStudyListItem {

        public Integer id;
        public String name;
        public String description;
        public String createdBy;
        public Integer indexCohortId;
        public Integer matchingCohortId;
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd, HH:mm")
        public Date createdDate;
        public String modifiedBy;
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd, HH:mm")
        public Date modifiedDate;
    }

    public static class FeasibilityStudyDTO extends FeasibilityStudyListItem {

        public String indexRule;
        public String indexDescription;
        public List<InclusionRule> inclusionRules;
    }

    public static class StudyInfoDTO {
        public StudyGenerationInfo generationInfo;
        public FeasibilityReport.Summary summary;
    }

    private final RowMapper<FeasibilityReport.Summary> summaryMapper = new RowMapper<FeasibilityReport.Summary>() {
        @Override
        public FeasibilityReport.Summary mapRow(ResultSet rs, int rowNum) throws SQLException {
            FeasibilityReport.Summary summary = new FeasibilityReport.Summary();
            summary.totalPersons = rs.getLong("person_count");
            summary.matchingPersons = rs.getLong("match_count");

            double matchRatio = (summary.totalPersons > 0)
                    ? ((double) summary.matchingPersons / (double) summary.totalPersons)
                    : 0.0;
            summary.percentMatched = new BigDecimal(matchRatio * 100.0).setScale(2, RoundingMode.HALF_UP)
                    .toPlainString() + "%";
            return summary;
        }
    };

    private FeasibilityReport.Summary getSimulationSummary(int id, Source source) {

        String resultsTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Results);

        String summaryQuery = String.format(
                "select person_count, match_count from %s.feas_study_index_stats where study_id = %d",
                resultsTableQualifier, id);
        String translatedSql = SqlTranslate.translateSql(summaryQuery, "sql server", source.getSourceDialect(),
                SessionUtils.sessionId(), resultsTableQualifier);
        List<FeasibilityReport.Summary> summaryList = this.getSourceJdbcTemplate(source).query(translatedSql,
                summaryMapper);
        if (summaryList.size() > 0)
            return summaryList.get(0);

        return null;
    }

    private final RowMapper<FeasibilityReport.InclusionRuleStatistic> inclusionRuleStatisticMapper = new RowMapper<FeasibilityReport.InclusionRuleStatistic>() {

        @Override
        public FeasibilityReport.InclusionRuleStatistic mapRow(ResultSet rs, int rowNum) throws SQLException {
            FeasibilityReport.InclusionRuleStatistic statistic = new FeasibilityReport.InclusionRuleStatistic();
            statistic.id = rs.getInt("rule_sequence");
            statistic.name = rs.getString("name");
            statistic.countSatisfying = rs.getLong("person_count");
            long personTotal = rs.getLong("person_total");

            long gainCount = rs.getLong("gain_count");
            double excludeRatio = personTotal > 0 ? (double) gainCount / (double) personTotal : 0.0;
            String percentExcluded = new BigDecimal(excludeRatio * 100.0).setScale(2, RoundingMode.HALF_UP)
                    .toPlainString();
            statistic.percentExcluded = percentExcluded + "%";

            long satisfyCount = rs.getLong("person_count");
            double satisfyRatio = personTotal > 0 ? (double) satisfyCount / (double) personTotal : 0.0;
            String percentSatisfying = new BigDecimal(satisfyRatio * 100.0).setScale(2, RoundingMode.HALF_UP)
                    .toPlainString();
            statistic.percentSatisfying = percentSatisfying + "%";
            return statistic;
        }
    };

    private String getMatchingCriteriaExpression(FeasibilityStudy p) {

        if (p.getInclusionRules().size() == 0) {
            throw new RuntimeException("Study must have at least 1 inclusion rule");
        }

        try {
            // all resultRule repository objects are initalized; create 'all criteria' cohort definition from index rule + inclusion rules
            ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
            CohortExpression indexRuleExpression = mapper.readValue(p.getIndexRule().getDetails().getExpression(),
                    CohortExpression.class);

            if (indexRuleExpression.additionalCriteria == null) {
                CriteriaGroup additionalCriteria = new CriteriaGroup();
                additionalCriteria.type = "ALL";
                indexRuleExpression.additionalCriteria = additionalCriteria;
            } else {
                if (!"ALL".equalsIgnoreCase(indexRuleExpression.additionalCriteria.type)) {
                    // move this CriteriaGroup inside a new parent CriteriaGroup where the parent CriteriaGroup.type == "ALL"
                    CriteriaGroup parentGroup = new CriteriaGroup();
                    parentGroup.type = "ALL";
                    parentGroup.groups = new CriteriaGroup[1];
                    parentGroup.groups[0] = indexRuleExpression.additionalCriteria;
                    indexRuleExpression.additionalCriteria = parentGroup;
                }
            }
            // place each inclusion rule (which is a CriteriaGroup) in the indexRuleExpression.additionalCriteria.group array to create the 'allCriteriaExpression'
            ArrayList<CriteriaGroup> additionalCriteriaGroups = new ArrayList<>();
            if (indexRuleExpression.additionalCriteria.groups != null) {
                additionalCriteriaGroups.addAll(Arrays.asList(indexRuleExpression.additionalCriteria.groups));
            }

            for (InclusionRule inclusionRule : p.getInclusionRules()) {
                String inclusionRuleJSON = inclusionRule.getExpression();
                CriteriaGroup inclusionRuleGroup = mapper.readValue(inclusionRuleJSON, CriteriaGroup.class);
                additionalCriteriaGroups.add(inclusionRuleGroup);
            }
            // overwrite indexRule additional criteria groups with the new list of groups with inclusion rules
            indexRuleExpression.additionalCriteria.groups = additionalCriteriaGroups.toArray(new CriteriaGroup[0]);

            String allCriteriaExpression = mapper.writeValueAsString(indexRuleExpression); // index rule expression now contains all inclusion criteria as additional criteria
            return allCriteriaExpression;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private List<FeasibilityReport.InclusionRuleStatistic> getSimulationInclusionRuleStatistics(int id,
            Source source) {
        String resultsTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Results);
        String statisticsQuery = String.format(
                "select rule_sequence, name, person_count, gain_count, person_total from %s.feas_study_inclusion_stats where study_id = %d ORDER BY rule_sequence",
                resultsTableQualifier, id);
        String translatedSql = SqlTranslate.translateSql(statisticsQuery, "sql server", source.getSourceDialect(),
                SessionUtils.sessionId(), resultsTableQualifier);
        return this.getSourceJdbcTemplate(source).query(translatedSql, inclusionRuleStatisticMapper);
    }

    private int countSetBits(long n) {
        int count = 0;
        while (n > 0) {
            n &= (n - 1);
            count++;
        }
        return count;
    }

    private String formatBitMask(Long n, int size) {
        return StringUtils.reverse(StringUtils.leftPad(Long.toBinaryString(n), size, "0"));
    }

    private final RowMapper<Long[]> simulationResultItemMapper = new RowMapper<Long[]>() {

        @Override
        public Long[] mapRow(ResultSet rs, int rowNum) throws SQLException {
            Long[] resultItem = new Long[2];
            resultItem[0] = rs.getLong("inclusion_rule_mask");
            resultItem[1] = rs.getLong("person_count");
            return resultItem;
        }
    };

    private String getInclusionRuleTreemapData(int id, int inclusionRuleCount, Source source) {
        String resultsTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Results);
        String smulationResultsQuery = String.format(
                "select inclusion_rule_mask, person_count from %s.feas_study_result where study_id = %d",
                resultsTableQualifier, id);
        String translatedSql = SqlTranslate.translateSql(smulationResultsQuery, "sql server",
                source.getSourceDialect(), SessionUtils.sessionId(), resultsTableQualifier);

        // [0] is the inclusion rule bitmask, [1] is the count of the match
        List<Long[]> items = this.getSourceJdbcTemplate(source).query(translatedSql, simulationResultItemMapper);
        Map<Integer, List<Long[]>> groups = new HashMap<>();
        for (Long[] item : items) {
            int bitsSet = countSetBits(item[0]);
            if (!groups.containsKey(bitsSet)) {
                groups.put(bitsSet, new ArrayList<Long[]>());
            }
            groups.get(bitsSet).add(item);
        }

        StringBuilder treemapData = new StringBuilder("{\"name\" : \"Everyone\", \"children\" : [");

        List<Integer> groupKeys = new ArrayList<Integer>(groups.keySet());
        Collections.sort(groupKeys);
        Collections.reverse(groupKeys);

        int groupCount = 0;
        // create a nested treemap data where more matches (more bits set in string) appear higher in the hierarchy)
        for (Integer groupKey : groupKeys) {
            if (groupCount > 0) {
                treemapData.append(",");
            }

            treemapData.append(String.format("{\"name\" : \"Group %d\", \"children\" : [", groupKey));

            int groupItemCount = 0;
            for (Long[] groupItem : groups.get(groupKey)) {
                if (groupItemCount > 0) {
                    treemapData.append(",");
                }

                //sb_treemap.Append("{\"name\": \"" + cohort_identifer + "\", \"size\": " + cohorts[cohort_identifer].ToString() + "}");
                treemapData.append(String.format("{\"name\": \"%s\", \"size\": %d}",
                        formatBitMask(groupItem[0], inclusionRuleCount), groupItem[1]));
                groupItemCount++;
            }
            groupCount++;
        }

        treemapData.append(StringUtils.repeat("]}", groupCount + 1));

        return treemapData.toString();
    }

    public FeasibilityStudyDTO feasibilityStudyToDTO(FeasibilityStudy study) {
        FeasibilityStudyDTO pDTO = new FeasibilityStudyDTO();
        pDTO.id = study.getId();
        pDTO.name = study.getName();
        pDTO.description = study.getDescription();
        pDTO.indexCohortId = study.getIndexRule().getId();
        pDTO.matchingCohortId = study.getResultRule() != null ? study.getResultRule().getId() : null;
        pDTO.createdBy = study.getCreatedBy();
        pDTO.createdDate = study.getCreatedDate();
        pDTO.modifiedBy = study.getModifiedBy();
        pDTO.modifiedDate = study.getModifiedDate();
        pDTO.indexRule = study.getIndexRule().getDetails().getExpression();
        pDTO.indexDescription = study.getIndexRule().getDescription();
        pDTO.inclusionRules = study.getInclusionRules();

        return pDTO;
    }

    /**
     * Returns all cohort definitions in the cohort schema
     *
     * @return List of cohort_definition
     */
    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public List<FeasibilityService.FeasibilityStudyListItem> getFeasibilityStudyList() {
        ArrayList<FeasibilityService.FeasibilityStudyListItem> result = new ArrayList<>();
        Iterable<FeasibilityStudy> studies = this.feasibilityStudyRepository.findAll();
        for (FeasibilityStudy p : studies) {
            FeasibilityService.FeasibilityStudyListItem item = new FeasibilityService.FeasibilityStudyListItem();
            item.id = p.getId();
            item.name = p.getName();
            item.description = p.getDescription();
            item.indexCohortId = p.getIndexRule().getId();
            item.matchingCohortId = p.getResultRule() != null ? p.getResultRule().getId() : null;
            item.createdBy = p.getCreatedBy();
            item.createdDate = p.getCreatedDate();
            item.modifiedBy = p.getModifiedBy();
            item.modifiedDate = p.getModifiedDate();
            result.add(item);
        }
        return result;
    }

    /**
     * Creates the feasibility study
     *
     * @param study The study to create.
     * @return The new FeasibilityStudy
     */
    @PUT
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @Transactional
    public FeasibilityService.FeasibilityStudyDTO createStudy(FeasibilityService.FeasibilityStudyDTO study) {
        Date currentTime = Calendar.getInstance().getTime();

        //create definition in 2 saves, first to get the generated ID for the new cohort definition (the index rule)
        // then to associate the new definition with the index rule of the study
        FeasibilityStudy newStudy = new FeasibilityStudy();
        newStudy.setName(study.name).setDescription(study.description).setCreatedBy(security.getSubject())
                .setCreatedDate(currentTime).setInclusionRules(new ArrayList<InclusionRule>(study.inclusionRules));

        // create index cohort
        CohortDefinition indexRule = new CohortDefinition()
                .setName("Index Population for Study: " + newStudy.getName()).setDescription(study.indexDescription)
                .setCreatedBy(security.getSubject()).setCreatedDate(currentTime)
                .setExpressionType(ExpressionType.SIMPLE_EXPRESSION);

        CohortDefinitionDetails indexDetails = new CohortDefinitionDetails();
        indexDetails.setCohortDefinition(indexRule).setExpression(study.indexRule);
        indexRule.setDetails(indexDetails);
        newStudy.setIndexRule(indexRule);

        // build matching cohort from inclusion rules if inclusion rules exist
        if (newStudy.getInclusionRules().size() > 0) {
            CohortDefinition resultDef = new CohortDefinition()
                    .setName("Matching Population for Study: " + newStudy.getName())
                    .setDescription(newStudy.getDescription()).setCreatedBy(security.getSubject())
                    .setCreatedDate(currentTime).setExpressionType(ExpressionType.SIMPLE_EXPRESSION);

            CohortDefinitionDetails resultDetails = new CohortDefinitionDetails();
            resultDetails.setCohortDefinition(resultDef).setExpression(getMatchingCriteriaExpression(newStudy));
            resultDef.setDetails(resultDetails);
            newStudy.setResultRule(resultDef);
        }

        FeasibilityStudy createdStudy = this.feasibilityStudyRepository.save(newStudy);

        return feasibilityStudyToDTO(createdStudy);
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @Transactional(readOnly = true)
    public FeasibilityService.FeasibilityStudyDTO getStudy(@PathParam("id") final int id) {
        FeasibilityStudy s = this.feasibilityStudyRepository.findOneWithDetail(id);
        return feasibilityStudyToDTO(s);
    }

    @PUT
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional
    public FeasibilityService.FeasibilityStudyDTO saveStudy(@PathParam("id") final int id,
            FeasibilityStudyDTO study) {
        Date currentTime = Calendar.getInstance().getTime();

        FeasibilityStudy updatedStudy = this.feasibilityStudyRepository.findOne(id);
        updatedStudy.setName(study.name).setDescription(study.description).setModifiedBy(security.getSubject())
                .setModifiedDate(currentTime).setInclusionRules(study.inclusionRules);

        updatedStudy.getIndexRule().setModifiedBy(security.getSubject()).setModifiedDate(currentTime)
                .setName("Index Population for Study: " + updatedStudy.getName())
                .setDescription(study.indexDescription).getDetails().setExpression(study.indexRule);

        CohortDefinition resultRule = updatedStudy.getResultRule();
        if (updatedStudy.getInclusionRules().size() > 0) {
            if (resultRule == null) {
                resultRule = new CohortDefinition();
                resultRule.setName("Matching Population for Study: " + updatedStudy.getName())
                        .setCreatedBy(security.getSubject()).setCreatedDate(currentTime)
                        .setExpressionType(ExpressionType.SIMPLE_EXPRESSION);

                CohortDefinitionDetails resultDetails = new CohortDefinitionDetails();
                resultDetails.setCohortDefinition(resultRule);
                resultRule.setDetails(resultDetails);
                updatedStudy.setResultRule(resultRule);
            }

            resultRule.setModifiedBy(security.getSubject()).setModifiedDate(currentTime)
                    .setName("Matching Population for Study: " + updatedStudy.getName())
                    .setDescription(updatedStudy.getDescription()).setModifiedBy(security.getSubject())
                    .setModifiedDate(currentTime).getDetails()
                    .setExpression(getMatchingCriteriaExpression(updatedStudy));
        } else {
            updatedStudy.setResultRule(null);
            if (resultRule != null) {
                cohortDefinitionRepository.delete(resultRule);
            }
        }
        this.feasibilityStudyRepository.save(updatedStudy);

        return getStudy(id);
    }

    @GET
    @Path("/{study_id}/generate/{sourceKey}")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public JobExecutionResource performStudy(@PathParam("study_id") final int study_id,
            @PathParam("sourceKey") final String sourceKey) {
        Date startTime = Calendar.getInstance().getTime();

        Source source = this.getSourceRepository().findBySourceKey(sourceKey);
        String resultsTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Results);
        String cdmTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.CDM);

        DefaultTransactionDefinition requresNewTx = new DefaultTransactionDefinition();
        requresNewTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

        TransactionStatus initStatus = this.getTransactionTemplate().getTransactionManager()
                .getTransaction(requresNewTx);

        FeasibilityStudy study = this.feasibilityStudyRepository.findOne(study_id);

        CohortDefinition indexRule = this.cohortDefinitionRepository.findOne(study.getIndexRule().getId());
        CohortGenerationInfo indexInfo = findCohortGenerationInfoBySourceId(indexRule.getGenerationInfoList(),
                source.getSourceId());
        if (indexInfo == null) {
            indexInfo = new CohortGenerationInfo(indexRule, source.getSourceId());
            indexRule.getGenerationInfoList().add(indexInfo);
        }
        indexInfo.setStatus(GenerationStatus.PENDING).setStartTime(startTime).setExecutionDuration(null);
        this.cohortDefinitionRepository.save(indexRule);

        if (study.getResultRule() != null) {
            CohortDefinition resultRule = this.cohortDefinitionRepository.findOne(study.getResultRule().getId());
            CohortGenerationInfo resultInfo = findCohortGenerationInfoBySourceId(resultRule.getGenerationInfoList(),
                    source.getSourceId());
            if (resultInfo == null) {
                resultInfo = new CohortGenerationInfo(resultRule, source.getSourceId());
                resultRule.getGenerationInfoList().add(resultInfo);
            }
            resultInfo.setStatus(GenerationStatus.PENDING).setStartTime(startTime).setExecutionDuration(null);
            this.cohortDefinitionRepository.save(resultRule);
        }

        StudyGenerationInfo studyInfo = findStudyGenerationInfoBySourceId(study.getStudyGenerationInfoList(),
                source.getSourceId());
        if (studyInfo == null) {
            studyInfo = new StudyGenerationInfo(study, source);
            study.getStudyGenerationInfoList().add(studyInfo);
        }
        studyInfo.setStatus(GenerationStatus.PENDING).setStartTime(startTime).setExecutionDuration(null);

        this.feasibilityStudyRepository.save(study);

        this.getTransactionTemplate().getTransactionManager().commit(initStatus);

        JobParametersBuilder builder = new JobParametersBuilder();
        builder.addString("jobName", "performing feasibility study on " + indexRule.getName() + " : "
                + source.getSourceName() + " (" + source.getSourceKey() + ")");
        builder.addString("cdm_database_schema", cdmTableQualifier);
        builder.addString("results_database_schema", resultsTableQualifier);
        builder.addString("target_database_schema", resultsTableQualifier);
        builder.addString("target_dialect", source.getSourceDialect());
        builder.addString("target_table", "cohort");
        builder.addString("cohort_definition_id", ("" + indexRule.getId()));
        builder.addString("study_id", ("" + study_id));
        builder.addString("source_id", ("" + source.getSourceId()));
        builder.addString("generate_stats", Boolean.TRUE.toString());

        final JobParameters jobParameters = builder.toJobParameters();
        final JdbcTemplate sourceJdbcTemplate = getSourceJdbcTemplate(source);

        GenerateCohortTasklet indexRuleTasklet = new GenerateCohortTasklet(sourceJdbcTemplate,
                getTransactionTemplate(), cohortDefinitionRepository);

        Step generateCohortStep = stepBuilders.get("performStudy.generateIndexCohort").tasklet(indexRuleTasklet)
                .exceptionHandler(new TerminateJobStepExceptionHandler()).build();

        PerformFeasibilityTasklet simulateTasket = new PerformFeasibilityTasklet(sourceJdbcTemplate,
                getTransactionTemplate(), feasibilityStudyRepository, cohortDefinitionRepository);

        Step performStudyStep = stepBuilders.get("performStudy.performStudy").tasklet(simulateTasket).build();

        Job performStudyJob = jobBuilders.get("performStudy").start(generateCohortStep).next(performStudyStep)
                .build();

        JobExecutionResource jobExec = this.jobTemplate.launch(performStudyJob, jobParameters);
        return jobExec;
    }

    @GET
    @Path("/{id}/info")
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional(readOnly = true)
    public List<StudyInfoDTO> getSimulationInfo(@PathParam("id") final int id) {
        FeasibilityStudy study = this.feasibilityStudyRepository.findOne(id);

        List<StudyInfoDTO> result = new ArrayList<>();
        for (StudyGenerationInfo generationInfo : study.getStudyGenerationInfoList()) {
            StudyInfoDTO info = new StudyInfoDTO();
            info.generationInfo = generationInfo;
            info.summary = getSimulationSummary(id, generationInfo.getSource());
            result.add(info);
        }
        return result;
    }

    @GET
    @Path("/{id}/report/{sourceKey}")
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional
    public FeasibilityReport getSimulationReport(@PathParam("id") final int id,
            @PathParam("sourceKey") final String sourceKey) {

        Source source = this.getSourceRepository().findBySourceKey(sourceKey);

        FeasibilityReport.Summary summary = getSimulationSummary(id, source);
        List<FeasibilityReport.InclusionRuleStatistic> inclusionRuleStats = getSimulationInclusionRuleStatistics(id,
                source);
        String treemapData = getInclusionRuleTreemapData(id, inclusionRuleStats.size(), source);

        FeasibilityReport report = new FeasibilityReport();
        report.summary = summary;
        report.inclusionRuleStats = inclusionRuleStats;
        report.treemapData = treemapData;

        return report;
    }

    /**
     * Copies the specified cohort definition
     *
     * @param id - the Cohort Definition ID to copy
     * @return the copied cohort definition as a CohortDefinitionDTO
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}/copy")
    @javax.transaction.Transactional
    public FeasibilityStudyDTO copy(@PathParam("id") final int id) {
        FeasibilityStudyDTO sourceStudy = getStudy(id);
        sourceStudy.id = null; // clear the ID
        sourceStudy.name = "COPY OF: " + sourceStudy.name;

        FeasibilityStudyDTO copyStudy = createStudy(sourceStudy);
        return copyStudy;
    }

    /**
     * Deletes the specified cohort definition
     *
     * @param id - the Cohort Definition ID to copy
     */
    @DELETE
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public void delete(@PathParam("id") final int id) {
        feasibilityStudyRepository.delete(id);
    }

    /**
     * Deletes the specified cohort definition
     *
     * @param id - the Cohort Definition ID to copy
     */
    @DELETE
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}/info/{sourceKey}")
    @Transactional
    public void deleteInfo(@PathParam("id") final int id, @PathParam("sourceKey") final String sourceKey) {
        FeasibilityStudy study = feasibilityStudyRepository.findOne(id);
        StudyGenerationInfo itemToRemove = null;
        for (StudyGenerationInfo info : study.getStudyGenerationInfoList()) {
            if (info.getSource().getSourceKey().equals(sourceKey))
                itemToRemove = info;
        }

        if (itemToRemove != null)
            study.getStudyGenerationInfoList().remove(itemToRemove);

        feasibilityStudyRepository.save(study);
    }

}