<commit_before>package org.ihtsdo.otf.mapping.jpa.services;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;

import org.apache.log4j.Logger;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.ReaderUtil;
import org.apache.lucene.util.Version;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.indexes.IndexReaderAccessor;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.ihtsdo.otf.mapping.helpers.LocalException;
import org.ihtsdo.otf.mapping.helpers.MapProjectList;
import org.ihtsdo.otf.mapping.helpers.MapUserRole;
import org.ihtsdo.otf.mapping.helpers.PfsParameter;
import org.ihtsdo.otf.mapping.helpers.PfsParameterJpa;
import org.ihtsdo.otf.mapping.helpers.ReportDefinitionList;
import org.ihtsdo.otf.mapping.helpers.ReportDefinitionListJpa;
import org.ihtsdo.otf.mapping.helpers.ReportList;
import org.ihtsdo.otf.mapping.helpers.ReportListJpa;
import org.ihtsdo.otf.mapping.helpers.ReportQueryType;
import org.ihtsdo.otf.mapping.helpers.ReportResultItemList;
import org.ihtsdo.otf.mapping.helpers.ReportResultItemListJpa;
import org.ihtsdo.otf.mapping.helpers.SearchResult;
import org.ihtsdo.otf.mapping.helpers.SearchResultJpa;
import org.ihtsdo.otf.mapping.helpers.SearchResultList;
import org.ihtsdo.otf.mapping.helpers.SearchResultListJpa;
import org.ihtsdo.otf.mapping.model.MapProject;
import org.ihtsdo.otf.mapping.model.MapUser;
import org.ihtsdo.otf.mapping.reports.Report;
import org.ihtsdo.otf.mapping.reports.ReportDefinition;
import org.ihtsdo.otf.mapping.reports.ReportDefinitionJpa;
import org.ihtsdo.otf.mapping.reports.ReportJpa;
import org.ihtsdo.otf.mapping.reports.ReportNote;
import org.ihtsdo.otf.mapping.reports.ReportNoteJpa;
import org.ihtsdo.otf.mapping.reports.ReportResult;
import org.ihtsdo.otf.mapping.reports.ReportResultItem;
import org.ihtsdo.otf.mapping.reports.ReportResultItemJpa;
import org.ihtsdo.otf.mapping.reports.ReportResultJpa;
import org.ihtsdo.otf.mapping.services.MappingService;
import org.ihtsdo.otf.mapping.services.MetadataService;
import org.ihtsdo.otf.mapping.services.ReportService;

/**
 * JPA enabled implementation of {@link ReportService}.
 */
public class ReportServiceJpa extends RootServiceJpa implements ReportService {

  /** The map record indexed field names. */
  protected static Set<String> reportFieldNames;

  /**
   * Instantiates a new report service jpa.
   * 
   * @throws Exception the exception
   */
  public ReportServiceJpa() throws Exception {
    super();
    if (reportFieldNames == null) {
      initializeFieldNames();
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.RootService#initializeFieldNames()
   */
  @Override
  public synchronized void initializeFieldNames() throws Exception {
    reportFieldNames = new HashSet<>();
    EntityManager manager = factory.createEntityManager();
    FullTextEntityManager fullTextEntityManager =
        org.hibernate.search.jpa.Search.getFullTextEntityManager(manager);
    IndexReaderAccessor indexReaderAccessor =
        fullTextEntityManager.getSearchFactory().getIndexReaderAccessor();
    Set<String> indexedClassNames =
        fullTextEntityManager.getSearchFactory().getStatistics()
            .getIndexedClassNames();
    for (String indexClass : indexedClassNames) {
      if (indexClass.indexOf("ReportJpa") != 0) {
        IndexReader indexReader = indexReaderAccessor.open(indexClass);
        try {
          for (FieldInfo info : ReaderUtil.getMergedFieldInfos(indexReader)) {
            reportFieldNames.add(info.name);
          }
        } finally {
          indexReaderAccessor.close(indexReader);
        }
      }
    }
    fullTextEntityManager.close();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.jpa.services.RootServiceJpa#close()
   */
  @Override
  public void close() throws Exception {
    if (manager.isOpen()) {
      manager.close();
    }
  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportJpa
  // /////////////////////////////////////////////////////
  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReports()
   */
  @Override
  @SuppressWarnings("unchecked")
  public ReportList getReports() {

    List<Report> Reports = null;

    // construct query
    javax.persistence.Query query =
        manager.createQuery("select m from ReportJpa m");

    Reports = query.getResultList();

    // force instantiation of lazy collections
    for (Report Report : Reports) {
      handleReportLazyInitialization(Report);
    }

    ReportListJpa ReportList = new ReportListJpa();
    ReportList.setReports(Reports);
    ReportList.setTotalCount(Reports.size());
    return ReportList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#findReportsForQuery(java
   * .lang.String, org.ihtsdo.otf.mapping.helpers.PfsParameter)
   */
  @Override
  @SuppressWarnings("unchecked")
  public SearchResultList findReportsForQuery(String query,
    PfsParameter pfsParameter) throws Exception {

    SearchResultList s = new SearchResultListJpa();

    FullTextEntityManager fullTextEntityManager =
        Search.getFullTextEntityManager(manager);

    SearchFactory searchFactory = fullTextEntityManager.getSearchFactory();
    Query luceneQuery;

    try {
      // construct luceneQuery based on URL format
      if (query.indexOf(':') == -1) { // no fields indicated
        MultiFieldQueryParser queryParser =
            new MultiFieldQueryParser(Version.LUCENE_36,
                reportFieldNames.toArray(new String[0]),
                searchFactory.getAnalyzer(ReportJpa.class));
        queryParser.setAllowLeadingWildcard(false);
        luceneQuery = queryParser.parse(query);

      } else { // field:value
        QueryParser queryParser =
            new QueryParser(Version.LUCENE_36, "summary",
                searchFactory.getAnalyzer(ReportJpa.class));
        luceneQuery = queryParser.parse(query);
      }
    } catch (ParseException e) {
      throw new LocalException(
          "The specified search terms cannot be parsed.  Please check syntax and try again.");
    }

    List<Report> reports;

    reports =
        fullTextEntityManager.createFullTextQuery(luceneQuery, ReportJpa.class)
            .getResultList();
    // if a parse exception, throw a local exception

    Logger.getLogger(getClass()).debug(
        Integer.toString(reports.size()) + " reports retrieved");

    for (Report report : reports) {
      s.addSearchResult(new SearchResultJpa(report.getId(), null, report
          .getName(), ""));

    }

    // Sort by ID
    s.sortBy(new Comparator<SearchResult>() {
      @Override
      public int compare(SearchResult o1, SearchResult o2) {
        return o1.getId().compareTo(o2.getId());
      }
    });

    fullTextEntityManager.close();

    // closing fullTextEntityManager also closes manager, recreate
    manager = factory.createEntityManager();

    return s;

  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#getReport(java.lang.Long)
   */
  @Override
  public Report getReport(Long reportId) {
    Report r = null;

    javax.persistence.Query query =
        manager.createQuery("select r from ReportJpa r where id = :id");
    query.setParameter("id", reportId);

    r = (Report) query.getSingleResult();

    // handle lazy initializations
    r.getResults().size();
    r.getNotes().size();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#addReport(org.ihtsdo.otf
   * .mapping.reports.Report)
   */
  @Override
  public Report addReport(Report report) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(report);
      tx.commit();

      return report;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(report);
      return report;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#updateReport(org.ihtsdo
   * .otf.mapping.reports.Report)
   */
  @Override
  public void updateReport(Report report) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(report);
      tx.commit();
    } else {
      manager.merge(report);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#removeReport(java.lang.
   * Long)
   */
  @Override
  public void removeReport(Long reportId) {

    Report report = manager.find(ReportJpa.class, reportId);

    // now remove the entry
    tx.begin();
    if (manager.contains(report)) {
      manager.remove(report);
    } else {
      manager.remove(manager.merge(report));
    }
    tx.commit();

  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportResultJpa
  // /////////////////////////////////////////////////////

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#getReportResult(java.lang
   * .Long)
   */
  @Override
  public ReportResult getReportResult(Long reportResultId) {
    ReportResult r = null;

    javax.persistence.Query query =
        manager.createQuery("select r from ReportResultJpa r where id = :id");
    query.setParameter("id", reportResultId);

    r = (ReportResult) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#addReportResult(org.ihtsdo
   * .otf.mapping.reports.ReportResult)
   */
  @Override
  public ReportResult addReportResult(ReportResult reportResult) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportResult);
      tx.commit();

      return reportResult;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportResult);
      return reportResult;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#updateReportResult(org.
   * ihtsdo.otf.mapping.reports.ReportResult)
   */
  @Override
  public void updateReportResult(ReportResult reportResult) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportResult);
      tx.commit();
    } else {
      manager.merge(reportResult);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#removeReportResult(java
   * .lang.Long)
   */
  @Override
  public void removeReportResult(Long reportResultId) {

    ReportResult reportResult =
        manager.find(ReportResultJpa.class, reportResultId);

    // now remove the entry
    tx.begin();
    if (manager.contains(reportResult)) {
      manager.remove(reportResult);
    } else {
      manager.remove(manager.merge(reportResult));
    }
    tx.commit();

  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportResultItemJpa
  // /////////////////////////////////////////////////////

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportResultItem(java
   * .lang.Long)
   */
  @Override
  public ReportResultItem getReportResultItem(Long reportResultItemId) {
    ReportResultItem r = null;

    javax.persistence.Query query =
        manager
            .createQuery("select r from ReportResultItemJpa r where id = :id");
    query.setParameter("id", reportResultItemId);

    r = (ReportResultItem) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#addReportResultItem(org
   * .ihtsdo.otf.mapping.reports.ReportResultItem)
   */
  @Override
  public ReportResultItem addReportResultItem(ReportResultItem reportResultItem) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportResultItem);
      tx.commit();

      return reportResultItem;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportResultItem);
      return reportResultItem;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#updateReportResultItem(
   * org.ihtsdo.otf.mapping.reports.ReportResultItem)
   */
  @Override
  public void updateReportResultItem(ReportResultItem reportResultItem) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportResultItem);
      tx.commit();
    } else {
      manager.merge(reportResultItem);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#removeReportResultItem(
   * java.lang.Long)
   */
  @Override
  public void removeReportResultItem(Long reportResultItemId) {

    ReportResultItem reportResultItem =
        manager.find(ReportResultItemJpa.class, reportResultItemId);

    // now remove the entry
    tx.begin();
    if (manager.contains(reportResultItem)) {
      manager.remove(reportResultItem);
    } else {
      manager.remove(manager.merge(reportResultItem));
    }
    tx.commit();

  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportNoteJpa
  // /////////////////////////////////////////////////////

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportNote(java.lang
   * .Long)
   */
  @Override
  public ReportNote getReportNote(Long reportNoteId) {
    ReportNote r = null;

    javax.persistence.Query query =
        manager.createQuery("select r from ReportNoteJpa r where id = :id");
    query.setParameter("id", reportNoteId);

    r = (ReportNote) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#addReportNote(org.ihtsdo
   * .otf.mapping.reports.ReportNote)
   */
  @Override
  public ReportNote addReportNote(ReportNote reportNote) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportNote);
      tx.commit();

      return reportNote;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportNote);
      return reportNote;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#updateReportNote(org.ihtsdo
   * .otf.mapping.reports.ReportNote)
   */
  @Override
  public void updateReportNote(ReportNote reportNote) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportNote);
      tx.commit();
    } else {
      manager.merge(reportNote);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#removeReportNote(java.lang
   * .Long)
   */
  @Override
  public void removeReportNote(Long reportNoteId) {

    ReportNote reportNote = manager.find(ReportNoteJpa.class, reportNoteId);

    // now remove the entry
    tx.begin();
    if (manager.contains(reportNote)) {
      manager.remove(reportNote);
    } else {
      manager.remove(manager.merge(reportNote));
    }
    tx.commit();

  }

  /**
   * Handle report lazy initialization.
   * 
   * @param report the report
   */
  private void handleReportLazyInitialization(Report report) {
    report.getNotes().size();
    report.getResults().size();

    for (ReportResult result : report.getResults()) {
      result.getReportResultItems().size();
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#getReportDefinitionsForRole
   * (org.ihtsdo.otf.mapping.helpers.MapUserRole)
   */
  @SuppressWarnings("unchecked")
  @Override
  public ReportDefinitionList getReportDefinitionsForRole(MapUserRole role) {
    ReportDefinitionList definitionList = new ReportDefinitionListJpa();

    // get all definitions
    List<ReportDefinition> definitions =
        manager.createQuery(
            "select d from ReportDefinitionJpa d where isQACheck = false")
            .getResultList();

    // cycle over definitions and check if role has required privileges
    for (ReportDefinition definition : definitions) {
      if (role.hasPrivilegesOf(definition.getRoleRequired()))
        definitionList.addReportDefinition(definition);
    }

    // return the definition list
    return definitionList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportDefinitions()
   */
  @Override
  @SuppressWarnings("unchecked")
  public ReportDefinitionList getReportDefinitions() {

    List<ReportDefinition> ReportDefinitions = null;

    // construct query
    javax.persistence.Query query =
        manager
            .createQuery("select m from ReportDefinitionJpa m where isQACheck = false");

    ReportDefinitions = query.getResultList();

    ReportDefinitionListJpa ReportDefinitionList =
        new ReportDefinitionListJpa();
    ReportDefinitionList.setReportDefinitions(ReportDefinitions);
    ReportDefinitionList.setTotalCount(ReportDefinitions.size());
    return ReportDefinitionList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportDefinitionService#getReportDefinition
   * (java.lang.Long)
   */
  @Override
  public ReportDefinition getReportDefinition(Long reportDefinitionId) {
    ReportDefinition r = null;

    javax.persistence.Query query =
        manager
            .createQuery("select r from ReportDefinitionJpa r where id = :id");
    query.setParameter("id", reportDefinitionId);

    r = (ReportDefinition) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportDefinitionService#addReportDefinition
   * (org.ihtsdo.otf .mapping.reportDefinitions.ReportDefinition)
   */
  @Override
  public ReportDefinition addReportDefinition(ReportDefinition reportDefinition) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportDefinition);
      tx.commit();

      return reportDefinition;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportDefinition);
      return reportDefinition;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportDefinitionService#
   * updateReportDefinition(org.ihtsdo
   * .otf.mapping.reportDefinitions.ReportDefinition)
   */
  @Override
  public void updateReportDefinition(ReportDefinition reportDefinition) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportDefinition);
      tx.commit();
    } else {
      manager.merge(reportDefinition);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportDefinitionService#
   * removeReportDefinition(java.lang. Long)
   */
  @Override
  public void removeReportDefinition(Long reportDefinitionId) throws Exception {

    ReportDefinition reportDefinition =
        manager.find(ReportDefinitionJpa.class, reportDefinitionId);

    // check if this definition is used by map projects
    MappingService mappingService = new MappingServiceJpa();
    MapProjectList mapProjects = mappingService.getMapProjects();
    mappingService.close();

    for (MapProject mapProject : mapProjects.getIterable()) {
      if (mapProject.getReportDefinitions().contains(reportDefinition)) {
        throw new LocalException(
            "Report definition is currently in use by project "
                + mapProject.getName() + " and cannot be deleted");
      }
    }

    // now remove the entry
    tx.begin();
    if (manager.contains(reportDefinition)) {
      manager.remove(reportDefinition);
    } else {
      manager.remove(manager.merge(reportDefinition));
    }
    tx.commit();

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportsForMapProject
   * (org.ihtsdo.otf.mapping.model.MapProject,
   * org.ihtsdo.otf.mapping.helpers.PfsParameter)
   */
  @Override
  public ReportList getReportsForMapProject(MapProject mapProject,
    PfsParameter pfsParameter) {

    return getReportsForMapProjectAndReportDefinition(mapProject, null,
        pfsParameter);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#
   * getReportsForMapProjectAndReportDefinition
   * (org.ihtsdo.otf.mapping.model.MapProject,
   * org.ihtsdo.otf.mapping.reports.ReportDefinition,
   * org.ihtsdo.otf.mapping.helpers.PfsParameter)
   */
  @SuppressWarnings("unchecked")
  @Override
  public ReportList getReportsForMapProjectAndReportDefinition(
    MapProject mapProject, ReportDefinition reportDefinition,
    PfsParameter pfsParameter) {

    Logger.getLogger(getClass()).info(
        "Retrieving reports for project "
            + mapProject.getName()
            + (reportDefinition == null ? "" : " of type "
                + reportDefinition.getName()));

    // instantiate empty paging/filtering/sorting object if null
    PfsParameter localPfsParameter = pfsParameter;
    if (localPfsParameter == null)
      localPfsParameter = new PfsParameterJpa();

    ReportList reportList = new ReportListJpa();

    // TODO: This is temporary, move this to a QueryBuilder structure
    // instead of using direct queries
    javax.persistence.Query query;

    // if definition supplied, return only results for that definition
    if (reportDefinition != null) {

      query =
          manager.createQuery(
              "select r from ReportJpa r "
                  + "where mapProjectId = :mapProjectId "
                  + "and reportDefinition_id = :reportDefinition_id "
                  + "order by timestamp desc").setParameter(
              "reportDefinition_id", reportDefinition.getId());

      // if no definition supplied, return all results, excluding QA
      // checks
    } else {
      query =
          manager
              .createQuery("select r from ReportJpa r, ReportDefinitionJpa d "
                  + "where mapProjectId = :mapProjectId "
                  + "and r.reportDefinition = d " + "and d.isQACheck = false "
                  + "order by timestamp desc");
    }

    // add project parameter for both cases
    query.setParameter("mapProjectId", mapProject.getId());

    // execute the query to get the number of reports
    // TODO This will be folded into lucene query builder later
    reportList.setTotalCount(query.getResultList().size());

    // if paging requested, set result parameters
    if (localPfsParameter.getStartIndex() != -1
        && localPfsParameter.getMaxResults() != -1) {
      query.setFirstResult(localPfsParameter.getStartIndex());
      query.setMaxResults(localPfsParameter.getMaxResults());
    }

    // reports are ALWAYS sorted in reverse order of date
    List<Report> reports = query.getResultList();

    for (Report report : reports)
      handleReportLazyInitialization(report);

    reportList.setReports(reports);

    Logger.getLogger(getClass()).info("  count = " + reportList.getCount());

    return reportList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#generateReportsForDateRange
   * (org.ihtsdo.otf.mapping.model.MapProject,
   * org.ihtsdo.otf.mapping.model.MapUser, java.util.Date, java.util.Date)
   */
  @Override
  public void generateReportsForDateRange(MapProject mapProject,
    MapUser mapUser, Date startDate, Date endDate) throws Exception {

    Calendar cal = Calendar.getInstance();

    // get all report definitions
    Set<ReportDefinition> reportDefinitions = mapProject.getReportDefinitions();

    // separate report definitions into daily and diff sets
    // note that this is necessary as diff reports require
    // a daily report to be present prior to calculation
    List<ReportDefinition> nonDiffReportDefinitions = new ArrayList<>();
    List<ReportDefinition> diffReportDefinitions = new ArrayList<>();

    // sort the report definitions into daily and diff sets
    for (ReportDefinition reportDefinition : reportDefinitions) {

      // if diff report, add to diff set
      if (reportDefinition.isDiffReport()) {
        diffReportDefinitions.add(reportDefinition);
      }
      // otherwise, if not a qa check, add to the normal reports
      else if (!reportDefinition.isQACheck()) {
        nonDiffReportDefinitions.add(reportDefinition);
      }
    }

    // handle reports in sort order
    Comparator<ReportDefinition> comp = new Comparator<ReportDefinition>() {
      @Override
      public int compare(ReportDefinition o1, ReportDefinition o2) {
        return o1.getName().compareTo(o2.getName());
      }
    };
    Collections.sort(nonDiffReportDefinitions, comp);
    Collections.sort(diffReportDefinitions, comp);

    // cycle over dates until end date is passed
    Date localStartDate = startDate;
    while (localStartDate.compareTo(endDate) <= 0) {

      cal.setTime(localStartDate);

      Logger.getLogger(getClass()).info(
          "  Generating reports for " + localStartDate);

      // first, do the non-diff reports
      // note that these must be calculated prior to diff reports
      for (ReportDefinition reportDefinition : nonDiffReportDefinitions) {

        if (isDateToRunReport(reportDefinition, localStartDate)) {

          // Only generate reports for those that have queries
          if (reportDefinition.getQueryType() != ReportQueryType.NONE) {
            Logger.getLogger(getClass()).info(
                "    Generating report " + reportDefinition.getName());

            Report report =
                generateReport(mapProject, mapUser, reportDefinition.getName(),
                    reportDefinition, localStartDate, true);

            // If generateReport returns a non-null result (no errors), add
            if (report != null) {
              Logger.getLogger(getClass()).info("     Persisting report.");
              addReport(report);
            }
          }
        }
      }

      // second, do the diff reports
      // note that this requires the current daily report to be present
      // (i.e. calculated above)
      for (ReportDefinition reportDefinition : diffReportDefinitions) {

        if (isDateToRunReport(reportDefinition, localStartDate)) {

          Logger.getLogger(getClass()).info(
              "    Generating report " + reportDefinition.getName());
          Report report;
          try {

            report =
                generateReport(mapProject, mapUser, reportDefinition.getName(),
                    reportDefinition, localStartDate, true);

            if (report != null) {
              Logger.getLogger(getClass()).info(
                  "     Persisting report " + report.toString());

              // persist the report
              report = addReport(report);
            } else {
              Logger.getLogger(ReportService.class).warn("    Skipping report");
            }
          } catch (LocalException e) {
            Logger.getLogger(getClass()).warn(e.getMessage());
          }

        }

      }

      // increment startDate by 1 day
      cal.setTime(localStartDate);
      cal.add(Calendar.DATE, 1);
      localStartDate = cal.getTime();
    }

  }

  /**
   * Helper function Given a report definition and a date, assesses whether this
   * report should be run, based on definition frequency
   * 
   * @param reportDefinition
   * @param date
   * @return
   * @throws Exception
   */
  private boolean isDateToRunReport(ReportDefinition reportDefinition, Date date)
    throws Exception {

    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    switch (reportDefinition.getFrequency()) {
      case DAILY:
      case ON_DEMAND:
        return true;
      case MONDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY);
      case TUESDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY);
      case WEDNESDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY);
      case THURSDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.THURSDAY);
      case FRIDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY);
      case SATURDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
      case SUNDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
      case FIRST_OF_MONTH:
        return cal.get(Calendar.DAY_OF_MONTH) == 1;
      case MID_MONTH:
        return cal.get(Calendar.DAY_OF_MONTH) == 15;
      case LAST_OF_MONTH:
        return cal.get(Calendar.DAY_OF_MONTH) == cal
            .getActualMaximum(Calendar.DAY_OF_MONTH);

      default:
        throw new Exception("Report definition found with invalid time period.");
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#generateDailyReports(org
   * .ihtsdo.otf.mapping.model.MapProject, org.ihtsdo.otf.mapping.model.MapUser)
   */
  @Override
  public void generateDailyReports(MapProject mapProject, MapUser mapUser)
    throws Exception {

    // get today's date
    Date date = new Date();

    // call date range report generation with start and end date as today
    generateReportsForDateRange(mapProject, mapUser, date, date);

  }

  // /////////////////////////////////////////////////////
  // Report Generation Service
  // /////////////////////////////////////////////////////
  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#generateReport(org.ihtsdo
   * .otf.mapping.model.MapProject, org.ihtsdo.otf.mapping.model.MapUser,
   * java.lang.String, org.ihtsdo.otf.mapping.reports.ReportDefinition,
   * java.util.Date, boolean)
   */
  @Override
  public Report generateReport(MapProject mapProject, MapUser owner,
    String name, ReportDefinition reportDefinition, Date date,
    boolean autoGenerated) throws Exception {

    Logger.getLogger(getClass()).info(
        "Generating report " + name + " for report definition "
            + reportDefinition.getName() + " for date " + date.toString());

    // instantiate the calendar object
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    // get the query to replace parameterized values
    String query = reportDefinition.getQuery();

    // if a diff report, need to construct a query based on specified report
    // definition
    if (reportDefinition.isDiffReport()) {

      query =
      // most recent report before or on specified date
          "select 'Report' value, name itemName, id itemId "
              + "from reports where name = '"
              + reportDefinition.getDiffReportDefinitionName()
              + "' "
              + "and mapProjectId = :MAP_PROJECT_ID: "
              + "and timestamp = "
              + "(select max(timestamp) from reports where timestamp < :TIMESTAMP:) "
              + "limit 1 "

              // union with most recent report before or on specified date less
              // interval<commit_msg>Small fix to diff report logic.<commit_after>package org.ihtsdo.otf.mapping.jpa.services;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;

import org.apache.log4j.Logger;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.ReaderUtil;
import org.apache.lucene.util.Version;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.indexes.IndexReaderAccessor;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.ihtsdo.otf.mapping.helpers.LocalException;
import org.ihtsdo.otf.mapping.helpers.MapProjectList;
import org.ihtsdo.otf.mapping.helpers.MapUserRole;
import org.ihtsdo.otf.mapping.helpers.PfsParameter;
import org.ihtsdo.otf.mapping.helpers.PfsParameterJpa;
import org.ihtsdo.otf.mapping.helpers.ReportDefinitionList;
import org.ihtsdo.otf.mapping.helpers.ReportDefinitionListJpa;
import org.ihtsdo.otf.mapping.helpers.ReportList;
import org.ihtsdo.otf.mapping.helpers.ReportListJpa;
import org.ihtsdo.otf.mapping.helpers.ReportQueryType;
import org.ihtsdo.otf.mapping.helpers.ReportResultItemList;
import org.ihtsdo.otf.mapping.helpers.ReportResultItemListJpa;
import org.ihtsdo.otf.mapping.helpers.SearchResult;
import org.ihtsdo.otf.mapping.helpers.SearchResultJpa;
import org.ihtsdo.otf.mapping.helpers.SearchResultList;
import org.ihtsdo.otf.mapping.helpers.SearchResultListJpa;
import org.ihtsdo.otf.mapping.model.MapProject;
import org.ihtsdo.otf.mapping.model.MapUser;
import org.ihtsdo.otf.mapping.reports.Report;
import org.ihtsdo.otf.mapping.reports.ReportDefinition;
import org.ihtsdo.otf.mapping.reports.ReportDefinitionJpa;
import org.ihtsdo.otf.mapping.reports.ReportJpa;
import org.ihtsdo.otf.mapping.reports.ReportNote;
import org.ihtsdo.otf.mapping.reports.ReportNoteJpa;
import org.ihtsdo.otf.mapping.reports.ReportResult;
import org.ihtsdo.otf.mapping.reports.ReportResultItem;
import org.ihtsdo.otf.mapping.reports.ReportResultItemJpa;
import org.ihtsdo.otf.mapping.reports.ReportResultJpa;
import org.ihtsdo.otf.mapping.services.MappingService;
import org.ihtsdo.otf.mapping.services.MetadataService;
import org.ihtsdo.otf.mapping.services.ReportService;

/**
 * JPA enabled implementation of {@link ReportService}.
 */
public class ReportServiceJpa extends RootServiceJpa implements ReportService {

  /** The map record indexed field names. */
  protected static Set<String> reportFieldNames;

  /**
   * Instantiates a new report service jpa.
   * 
   * @throws Exception the exception
   */
  public ReportServiceJpa() throws Exception {
    super();
    if (reportFieldNames == null) {
      initializeFieldNames();
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.RootService#initializeFieldNames()
   */
  @Override
  public synchronized void initializeFieldNames() throws Exception {
    reportFieldNames = new HashSet<>();
    EntityManager manager = factory.createEntityManager();
    FullTextEntityManager fullTextEntityManager =
        org.hibernate.search.jpa.Search.getFullTextEntityManager(manager);
    IndexReaderAccessor indexReaderAccessor =
        fullTextEntityManager.getSearchFactory().getIndexReaderAccessor();
    Set<String> indexedClassNames =
        fullTextEntityManager.getSearchFactory().getStatistics()
            .getIndexedClassNames();
    for (String indexClass : indexedClassNames) {
      if (indexClass.indexOf("ReportJpa") != 0) {
        IndexReader indexReader = indexReaderAccessor.open(indexClass);
        try {
          for (FieldInfo info : ReaderUtil.getMergedFieldInfos(indexReader)) {
            reportFieldNames.add(info.name);
          }
        } finally {
          indexReaderAccessor.close(indexReader);
        }
      }
    }
    fullTextEntityManager.close();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.jpa.services.RootServiceJpa#close()
   */
  @Override
  public void close() throws Exception {
    if (manager.isOpen()) {
      manager.close();
    }
  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportJpa
  // /////////////////////////////////////////////////////
  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReports()
   */
  @Override
  @SuppressWarnings("unchecked")
  public ReportList getReports() {

    List<Report> Reports = null;

    // construct query
    javax.persistence.Query query =
        manager.createQuery("select m from ReportJpa m");

    Reports = query.getResultList();

    // force instantiation of lazy collections
    for (Report Report : Reports) {
      handleReportLazyInitialization(Report);
    }

    ReportListJpa ReportList = new ReportListJpa();
    ReportList.setReports(Reports);
    ReportList.setTotalCount(Reports.size());
    return ReportList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#findReportsForQuery(java
   * .lang.String, org.ihtsdo.otf.mapping.helpers.PfsParameter)
   */
  @Override
  @SuppressWarnings("unchecked")
  public SearchResultList findReportsForQuery(String query,
    PfsParameter pfsParameter) throws Exception {

    SearchResultList s = new SearchResultListJpa();

    FullTextEntityManager fullTextEntityManager =
        Search.getFullTextEntityManager(manager);

    SearchFactory searchFactory = fullTextEntityManager.getSearchFactory();
    Query luceneQuery;

    try {
      // construct luceneQuery based on URL format
      if (query.indexOf(':') == -1) { // no fields indicated
        MultiFieldQueryParser queryParser =
            new MultiFieldQueryParser(Version.LUCENE_36,
                reportFieldNames.toArray(new String[0]),
                searchFactory.getAnalyzer(ReportJpa.class));
        queryParser.setAllowLeadingWildcard(false);
        luceneQuery = queryParser.parse(query);

      } else { // field:value
        QueryParser queryParser =
            new QueryParser(Version.LUCENE_36, "summary",
                searchFactory.getAnalyzer(ReportJpa.class));
        luceneQuery = queryParser.parse(query);
      }
    } catch (ParseException e) {
      throw new LocalException(
          "The specified search terms cannot be parsed.  Please check syntax and try again.");
    }

    List<Report> reports;

    reports =
        fullTextEntityManager.createFullTextQuery(luceneQuery, ReportJpa.class)
            .getResultList();
    // if a parse exception, throw a local exception

    Logger.getLogger(getClass()).debug(
        Integer.toString(reports.size()) + " reports retrieved");

    for (Report report : reports) {
      s.addSearchResult(new SearchResultJpa(report.getId(), null, report
          .getName(), ""));

    }

    // Sort by ID
    s.sortBy(new Comparator<SearchResult>() {
      @Override
      public int compare(SearchResult o1, SearchResult o2) {
        return o1.getId().compareTo(o2.getId());
      }
    });

    fullTextEntityManager.close();

    // closing fullTextEntityManager also closes manager, recreate
    manager = factory.createEntityManager();

    return s;

  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#getReport(java.lang.Long)
   */
  @Override
  public Report getReport(Long reportId) {
    Report r = null;

    javax.persistence.Query query =
        manager.createQuery("select r from ReportJpa r where id = :id");
    query.setParameter("id", reportId);

    r = (Report) query.getSingleResult();

    // handle lazy initializations
    r.getResults().size();
    r.getNotes().size();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#addReport(org.ihtsdo.otf
   * .mapping.reports.Report)
   */
  @Override
  public Report addReport(Report report) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(report);
      tx.commit();

      return report;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(report);
      return report;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#updateReport(org.ihtsdo
   * .otf.mapping.reports.Report)
   */
  @Override
  public void updateReport(Report report) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(report);
      tx.commit();
    } else {
      manager.merge(report);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#removeReport(java.lang.
   * Long)
   */
  @Override
  public void removeReport(Long reportId) {

    Report report = manager.find(ReportJpa.class, reportId);

    // now remove the entry
    tx.begin();
    if (manager.contains(report)) {
      manager.remove(report);
    } else {
      manager.remove(manager.merge(report));
    }
    tx.commit();

  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportResultJpa
  // /////////////////////////////////////////////////////

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#getReportResult(java.lang
   * .Long)
   */
  @Override
  public ReportResult getReportResult(Long reportResultId) {
    ReportResult r = null;

    javax.persistence.Query query =
        manager.createQuery("select r from ReportResultJpa r where id = :id");
    query.setParameter("id", reportResultId);

    r = (ReportResult) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#addReportResult(org.ihtsdo
   * .otf.mapping.reports.ReportResult)
   */
  @Override
  public ReportResult addReportResult(ReportResult reportResult) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportResult);
      tx.commit();

      return reportResult;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportResult);
      return reportResult;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#updateReportResult(org.
   * ihtsdo.otf.mapping.reports.ReportResult)
   */
  @Override
  public void updateReportResult(ReportResult reportResult) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportResult);
      tx.commit();
    } else {
      manager.merge(reportResult);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#removeReportResult(java
   * .lang.Long)
   */
  @Override
  public void removeReportResult(Long reportResultId) {

    ReportResult reportResult =
        manager.find(ReportResultJpa.class, reportResultId);

    // now remove the entry
    tx.begin();
    if (manager.contains(reportResult)) {
      manager.remove(reportResult);
    } else {
      manager.remove(manager.merge(reportResult));
    }
    tx.commit();

  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportResultItemJpa
  // /////////////////////////////////////////////////////

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportResultItem(java
   * .lang.Long)
   */
  @Override
  public ReportResultItem getReportResultItem(Long reportResultItemId) {
    ReportResultItem r = null;

    javax.persistence.Query query =
        manager
            .createQuery("select r from ReportResultItemJpa r where id = :id");
    query.setParameter("id", reportResultItemId);

    r = (ReportResultItem) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#addReportResultItem(org
   * .ihtsdo.otf.mapping.reports.ReportResultItem)
   */
  @Override
  public ReportResultItem addReportResultItem(ReportResultItem reportResultItem) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportResultItem);
      tx.commit();

      return reportResultItem;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportResultItem);
      return reportResultItem;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#updateReportResultItem(
   * org.ihtsdo.otf.mapping.reports.ReportResultItem)
   */
  @Override
  public void updateReportResultItem(ReportResultItem reportResultItem) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportResultItem);
      tx.commit();
    } else {
      manager.merge(reportResultItem);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#removeReportResultItem(
   * java.lang.Long)
   */
  @Override
  public void removeReportResultItem(Long reportResultItemId) {

    ReportResultItem reportResultItem =
        manager.find(ReportResultItemJpa.class, reportResultItemId);

    // now remove the entry
    tx.begin();
    if (manager.contains(reportResultItem)) {
      manager.remove(reportResultItem);
    } else {
      manager.remove(manager.merge(reportResultItem));
    }
    tx.commit();

  }

  // /////////////////////////////////////////////////////
  // CRUD Services for ReportNoteJpa
  // /////////////////////////////////////////////////////

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportNote(java.lang
   * .Long)
   */
  @Override
  public ReportNote getReportNote(Long reportNoteId) {
    ReportNote r = null;

    javax.persistence.Query query =
        manager.createQuery("select r from ReportNoteJpa r where id = :id");
    query.setParameter("id", reportNoteId);

    r = (ReportNote) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#addReportNote(org.ihtsdo
   * .otf.mapping.reports.ReportNote)
   */
  @Override
  public ReportNote addReportNote(ReportNote reportNote) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportNote);
      tx.commit();

      return reportNote;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportNote);
      return reportNote;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#updateReportNote(org.ihtsdo
   * .otf.mapping.reports.ReportNote)
   */
  @Override
  public void updateReportNote(ReportNote reportNote) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportNote);
      tx.commit();
    } else {
      manager.merge(reportNote);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#removeReportNote(java.lang
   * .Long)
   */
  @Override
  public void removeReportNote(Long reportNoteId) {

    ReportNote reportNote = manager.find(ReportNoteJpa.class, reportNoteId);

    // now remove the entry
    tx.begin();
    if (manager.contains(reportNote)) {
      manager.remove(reportNote);
    } else {
      manager.remove(manager.merge(reportNote));
    }
    tx.commit();

  }

  /**
   * Handle report lazy initialization.
   * 
   * @param report the report
   */
  private void handleReportLazyInitialization(Report report) {
    report.getNotes().size();
    report.getResults().size();

    for (ReportResult result : report.getResults()) {
      result.getReportResultItems().size();
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#getReportDefinitionsForRole
   * (org.ihtsdo.otf.mapping.helpers.MapUserRole)
   */
  @SuppressWarnings("unchecked")
  @Override
  public ReportDefinitionList getReportDefinitionsForRole(MapUserRole role) {
    ReportDefinitionList definitionList = new ReportDefinitionListJpa();

    // get all definitions
    List<ReportDefinition> definitions =
        manager.createQuery(
            "select d from ReportDefinitionJpa d where isQACheck = false")
            .getResultList();

    // cycle over definitions and check if role has required privileges
    for (ReportDefinition definition : definitions) {
      if (role.hasPrivilegesOf(definition.getRoleRequired()))
        definitionList.addReportDefinition(definition);
    }

    // return the definition list
    return definitionList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportDefinitions()
   */
  @Override
  @SuppressWarnings("unchecked")
  public ReportDefinitionList getReportDefinitions() {

    List<ReportDefinition> ReportDefinitions = null;

    // construct query
    javax.persistence.Query query =
        manager
            .createQuery("select m from ReportDefinitionJpa m where isQACheck = false");

    ReportDefinitions = query.getResultList();

    ReportDefinitionListJpa ReportDefinitionList =
        new ReportDefinitionListJpa();
    ReportDefinitionList.setReportDefinitions(ReportDefinitions);
    ReportDefinitionList.setTotalCount(ReportDefinitions.size());
    return ReportDefinitionList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportDefinitionService#getReportDefinition
   * (java.lang.Long)
   */
  @Override
  public ReportDefinition getReportDefinition(Long reportDefinitionId) {
    ReportDefinition r = null;

    javax.persistence.Query query =
        manager
            .createQuery("select r from ReportDefinitionJpa r where id = :id");
    query.setParameter("id", reportDefinitionId);

    r = (ReportDefinition) query.getSingleResult();

    return r;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportDefinitionService#addReportDefinition
   * (org.ihtsdo.otf .mapping.reportDefinitions.ReportDefinition)
   */
  @Override
  public ReportDefinition addReportDefinition(ReportDefinition reportDefinition) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.persist(reportDefinition);
      tx.commit();

      return reportDefinition;
    } else {
      if (!tx.isActive()) {
        throw new IllegalStateException(
            "Error attempting to change data without an active transaction");
      }
      manager.persist(reportDefinition);
      return reportDefinition;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportDefinitionService#
   * updateReportDefinition(org.ihtsdo
   * .otf.mapping.reportDefinitions.ReportDefinition)
   */
  @Override
  public void updateReportDefinition(ReportDefinition reportDefinition) {
    if (getTransactionPerOperation()) {
      tx = manager.getTransaction();
      tx.begin();
      manager.merge(reportDefinition);
      tx.commit();
    } else {
      manager.merge(reportDefinition);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportDefinitionService#
   * removeReportDefinition(java.lang. Long)
   */
  @Override
  public void removeReportDefinition(Long reportDefinitionId) throws Exception {

    ReportDefinition reportDefinition =
        manager.find(ReportDefinitionJpa.class, reportDefinitionId);

    // check if this definition is used by map projects
    MappingService mappingService = new MappingServiceJpa();
    MapProjectList mapProjects = mappingService.getMapProjects();
    mappingService.close();

    for (MapProject mapProject : mapProjects.getIterable()) {
      if (mapProject.getReportDefinitions().contains(reportDefinition)) {
        throw new LocalException(
            "Report definition is currently in use by project "
                + mapProject.getName() + " and cannot be deleted");
      }
    }

    // now remove the entry
    tx.begin();
    if (manager.contains(reportDefinition)) {
      manager.remove(reportDefinition);
    } else {
      manager.remove(manager.merge(reportDefinition));
    }
    tx.commit();

  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#getReportsForMapProject
   * (org.ihtsdo.otf.mapping.model.MapProject,
   * org.ihtsdo.otf.mapping.helpers.PfsParameter)
   */
  @Override
  public ReportList getReportsForMapProject(MapProject mapProject,
    PfsParameter pfsParameter) {

    return getReportsForMapProjectAndReportDefinition(mapProject, null,
        pfsParameter);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#
   * getReportsForMapProjectAndReportDefinition
   * (org.ihtsdo.otf.mapping.model.MapProject,
   * org.ihtsdo.otf.mapping.reports.ReportDefinition,
   * org.ihtsdo.otf.mapping.helpers.PfsParameter)
   */
  @SuppressWarnings("unchecked")
  @Override
  public ReportList getReportsForMapProjectAndReportDefinition(
    MapProject mapProject, ReportDefinition reportDefinition,
    PfsParameter pfsParameter) {

    Logger.getLogger(getClass()).info(
        "Retrieving reports for project "
            + mapProject.getName()
            + (reportDefinition == null ? "" : " of type "
                + reportDefinition.getName()));

    // instantiate empty paging/filtering/sorting object if null
    PfsParameter localPfsParameter = pfsParameter;
    if (localPfsParameter == null)
      localPfsParameter = new PfsParameterJpa();

    ReportList reportList = new ReportListJpa();

    // TODO: This is temporary, move this to a QueryBuilder structure
    // instead of using direct queries
    javax.persistence.Query query;

    // if definition supplied, return only results for that definition
    if (reportDefinition != null) {

      query =
          manager.createQuery(
              "select r from ReportJpa r "
                  + "where mapProjectId = :mapProjectId "
                  + "and reportDefinition_id = :reportDefinition_id "
                  + "order by timestamp desc").setParameter(
              "reportDefinition_id", reportDefinition.getId());

      // if no definition supplied, return all results, excluding QA
      // checks
    } else {
      query =
          manager
              .createQuery("select r from ReportJpa r, ReportDefinitionJpa d "
                  + "where mapProjectId = :mapProjectId "
                  + "and r.reportDefinition = d " + "and d.isQACheck = false "
                  + "order by timestamp desc");
    }

    // add project parameter for both cases
    query.setParameter("mapProjectId", mapProject.getId());

    // execute the query to get the number of reports
    // TODO This will be folded into lucene query builder later
    reportList.setTotalCount(query.getResultList().size());

    // if paging requested, set result parameters
    if (localPfsParameter.getStartIndex() != -1
        && localPfsParameter.getMaxResults() != -1) {
      query.setFirstResult(localPfsParameter.getStartIndex());
      query.setMaxResults(localPfsParameter.getMaxResults());
    }

    // reports are ALWAYS sorted in reverse order of date
    List<Report> reports = query.getResultList();

    for (Report report : reports)
      handleReportLazyInitialization(report);

    reportList.setReports(reports);

    Logger.getLogger(getClass()).info("  count = " + reportList.getCount());

    return reportList;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#generateReportsForDateRange
   * (org.ihtsdo.otf.mapping.model.MapProject,
   * org.ihtsdo.otf.mapping.model.MapUser, java.util.Date, java.util.Date)
   */
  @Override
  public void generateReportsForDateRange(MapProject mapProject,
    MapUser mapUser, Date startDate, Date endDate) throws Exception {

    Calendar cal = Calendar.getInstance();

    // get all report definitions
    Set<ReportDefinition> reportDefinitions = mapProject.getReportDefinitions();

    // separate report definitions into daily and diff sets
    // note that this is necessary as diff reports require
    // a daily report to be present prior to calculation
    List<ReportDefinition> nonDiffReportDefinitions = new ArrayList<>();
    List<ReportDefinition> diffReportDefinitions = new ArrayList<>();

    // sort the report definitions into daily and diff sets
    for (ReportDefinition reportDefinition : reportDefinitions) {

      // if diff report, add to diff set
      if (reportDefinition.isDiffReport()) {
        diffReportDefinitions.add(reportDefinition);
      }
      // otherwise, if not a qa check, add to the normal reports
      else if (!reportDefinition.isQACheck()) {
        nonDiffReportDefinitions.add(reportDefinition);
      }
    }

    // handle reports in sort order
    Comparator<ReportDefinition> comp = new Comparator<ReportDefinition>() {
      @Override
      public int compare(ReportDefinition o1, ReportDefinition o2) {
        return o1.getName().compareTo(o2.getName());
      }
    };
    Collections.sort(nonDiffReportDefinitions, comp);
    Collections.sort(diffReportDefinitions, comp);

    // cycle over dates until end date is passed
    Date localStartDate = startDate;
    while (localStartDate.compareTo(endDate) <= 0) {

      cal.setTime(localStartDate);

      Logger.getLogger(getClass()).info(
          "  Generating reports for " + localStartDate);

      // first, do the non-diff reports
      // note that these must be calculated prior to diff reports
      for (ReportDefinition reportDefinition : nonDiffReportDefinitions) {

        if (isDateToRunReport(reportDefinition, localStartDate)) {

          // Only generate reports for those that have queries
          if (reportDefinition.getQueryType() != ReportQueryType.NONE) {
            Logger.getLogger(getClass()).info(
                "    Generating report " + reportDefinition.getName());

            Report report =
                generateReport(mapProject, mapUser, reportDefinition.getName(),
                    reportDefinition, localStartDate, true);

            // If generateReport returns a non-null result (no errors), add
            if (report != null) {
              Logger.getLogger(getClass()).info("     Persisting report.");
              addReport(report);
            }
          }
        }
      }

      // second, do the diff reports
      // note that this requires the current daily report to be present
      // (i.e. calculated above)
      for (ReportDefinition reportDefinition : diffReportDefinitions) {

        if (isDateToRunReport(reportDefinition, localStartDate)) {

          Logger.getLogger(getClass()).info(
              "    Generating report " + reportDefinition.getName());
          Report report;
          try {

            report =
                generateReport(mapProject, mapUser, reportDefinition.getName(),
                    reportDefinition, localStartDate, true);

            if (report != null) {
              Logger.getLogger(getClass()).info(
                  "     Persisting report " + report.toString());

              // persist the report
              report = addReport(report);
            } else {
              Logger.getLogger(ReportService.class).warn("    Skipping report");
            }
          } catch (LocalException e) {
            Logger.getLogger(getClass()).warn(e.getMessage());
          }

        }

      }

      // increment startDate by 1 day
      cal.setTime(localStartDate);
      cal.add(Calendar.DATE, 1);
      localStartDate = cal.getTime();
    }

  }

  /**
   * Helper function Given a report definition and a date, assesses whether this
   * report should be run, based on definition frequency
   * 
   * @param reportDefinition
   * @param date
   * @return
   * @throws Exception
   */
  private boolean isDateToRunReport(ReportDefinition reportDefinition, Date date)
    throws Exception {

    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    switch (reportDefinition.getFrequency()) {
      case DAILY:
      case ON_DEMAND:
        return true;
      case MONDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY);
      case TUESDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY);
      case WEDNESDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY);
      case THURSDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.THURSDAY);
      case FRIDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY);
      case SATURDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
      case SUNDAY:
        return (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
      case FIRST_OF_MONTH:
        return cal.get(Calendar.DAY_OF_MONTH) == 1;
      case MID_MONTH:
        return cal.get(Calendar.DAY_OF_MONTH) == 15;
      case LAST_OF_MONTH:
        return cal.get(Calendar.DAY_OF_MONTH) == cal
            .getActualMaximum(Calendar.DAY_OF_MONTH);

      default:
        throw new Exception("Report definition found with invalid time period.");
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.ihtsdo.otf.mapping.services.ReportService#generateDailyReports(org
   * .ihtsdo.otf.mapping.model.MapProject, org.ihtsdo.otf.mapping.model.MapUser)
   */
  @Override
  public void generateDailyReports(MapProject mapProject, MapUser mapUser)
    throws Exception {

    // get today's date
    Date date = new Date();

    // call date range report generation with start and end date as today
    generateReportsForDateRange(mapProject, mapUser, date, date);

  }

  // /////////////////////////////////////////////////////
  // Report Generation Service
  // /////////////////////////////////////////////////////
  /*
   * (non-Javadoc)
   * 
   * @see
   * org.ihtsdo.otf.mapping.services.ReportService#generateReport(org.ihtsdo
   * .otf.mapping.model.MapProject, org.ihtsdo.otf.mapping.model.MapUser,
   * java.lang.String, org.ihtsdo.otf.mapping.reports.ReportDefinition,
   * java.util.Date, boolean)
   */
  @Override
  public Report generateReport(MapProject mapProject, MapUser owner,
    String name, ReportDefinition reportDefinition, Date date,
    boolean autoGenerated) throws Exception {

    Logger.getLogger(getClass()).info(
        "Generating report " + name + " for report definition "
            + reportDefinition.getName() + " for date " + date.toString());

    // instantiate the calendar object
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    // get the query to replace parameterized values
    String query = reportDefinition.getQuery();

    // if a diff report, need to construct a query based on specified report
    // definition
    if (reportDefinition.isDiffReport()) {

      query =
      // most recent report before or on specified date
          "select 'Report' value, name itemName, id itemId "
              + "from reports where name = '"
              + reportDefinition.getDiffReportDefinitionName()
              + "' "
              + "and mapProjectId = :MAP_PROJECT_ID: "
              + "and timestamp = "
              + "(select max(timestamp) from reports where timestamp <= :TIMESTAMP:) "
              + "limit 1 "

              // union with most recent report before or on specified date less
              // interval
{'fraction_non_alphanumeric': 0.1218672308096172, 'fraction_numerical': 0.0007720934541916954, 'mean_word_length': 2.7405417894068043, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3406933', 'n_tokens_mistral': 19178, 'n_tokens_neox': 18575, 'n_words': 3684}