KPIApiServiceImpl.java

/*
 * Copyright 2025 Global Crop Diversity Trust
 *
 * 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.genesys.server.api.v2.facade.impl;

import org.apache.commons.lang3.StringUtils;
import org.genesys.server.api.v2.facade.KPIApiService;
import org.genesys.server.api.v2.mapper.MapstructMapper;
import org.genesys.server.api.v2.model.kpi.DimensionDTO;
import org.genesys.server.api.v2.model.kpi.ExecutionDTO;
import org.genesys.server.api.v2.model.kpi.ExecutionInfo;
import org.genesys.server.api.v2.model.kpi.ExecutionRunDTO;
import org.genesys.server.api.v2.model.kpi.KPIParameterDTO;
import org.genesys.server.api.v2.model.kpi.ObservationDTO;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.model.kpi.Execution;
import org.genesys.server.model.kpi.ExecutionRun;
import org.genesys.server.model.kpi.Observation;
import org.genesys.server.service.KPIService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Service
@Transactional(readOnly = true)
public class KPIApiServiceImpl implements KPIApiService {

	@Autowired
	private MapstructMapper mapper;

	@Autowired
	private KPIService service;

	@Override
	@Transactional
	public KPIParameterDTO save(KPIParameterDTO parameter) {
		return mapper.map(service.save(mapper.map(parameter)));
	}

	@Override
	@Transactional
	public KPIParameterDTO deleteParameter(String name) {
		return mapper.map(service.delete(service.getParameter(name)));
	}

	@Override
	public Page<KPIParameterDTO> listParameters(Pageable page) {
		return mapper.map(service.listParameters(page), mapper::map);
	}

	@Override
	public DimensionDTO getDimension(String name) {
		return mapper.map(service.getDimension(name));
	}

	@Override
	@Transactional
	public DimensionDTO save(DimensionDTO dimension) {
		return mapper.map(service.save(mapper.map(dimension)));
	}

	@Override
	@Transactional
	public DimensionDTO deleteDimension(String name) {
		return mapper.map(service.delete(service.getDimension(name)));
	}

	@Override
	public Page<DimensionDTO> listDimensions(Pageable page) {
		return mapper.map(service.listDimensions(page), mapper::map);
	}

	@Override
	@Transactional
	public ExecutionDTO save(ExecutionDTO execution) {
		return mapper.map(service.save(mapper.map(execution)));
	}

	@Override
	@Transactional
	public ExecutionDTO deleteExecution(String name) {
		return mapper.map(service.delete(service.getExecution(name)));
	}

	@Override
	public Page<ExecutionDTO> listExecutions(Pageable page) {
		return mapper.map(service.listExecutions(page), mapper::map);
	}

	@Override
	@Transactional
	public ExecutionRunDTO runExecution(String name) {
		return mapper.map(service.executeAndSave(service.getExecution(name)));
	}

	@Override
	@Transactional
	public long purgeExecutionRuns(String name) {
		long deleted = service.purgeExecutionRuns(service.getExecution(name));
		System.err.println("Done deleting!");
		return deleted;
	}

	@Override
	public ExecutionDetailsDTO executionDetails(String name) {
		Execution execution = service.loadExecution(name);
		var details = KPIService.ExecutionDetails.from(execution, service.findLastExecutionRun(execution), service.listExecutionRuns(execution, PageRequest.of(0, 10)));
		var detailsDTO = new ExecutionDetailsDTO();
		detailsDTO.setExecution(mapper.map(execution));
		detailsDTO.setLastRun(mapper.map(details.lastRun));
		detailsDTO.setRuns(details.runs.map(mapper::map));
		return detailsDTO;
	}

	@Override
	public ExecutionInfo getExecution(String name) {
		return mapper.mapInfo(service.getExecution(name));
	}

	@Override
	public Page<ExecutionRunDTO> executionRuns(String name, Pageable page) {
		return mapper.map(service.listExecutionRuns(service.getExecution(name), page), mapper::map);
	}

	@Override
	public ExecutionRunDTO executionRunByDate(String name, LocalDate date) {
		return mapper.map(service.findExecutionRunByDate(service.getExecution(name), date));
	}

	@Override
	public ExecutionRunDTO executionRun(String name, long runId) {
		ExecutionRun run = service.getExecutionRun(runId);
		if (! StringUtils.equals(run.getExecution().getName(), name)) {
			throw new NotFoundElement("No run " + runId + " for execution " + name);
		}
		return mapper.map(run);
	}

	@Override
	@Transactional
	public ExecutionRunDTO deleteExecutionRun(String name, long runId) {
		var executionRun = service.getExecutionRun(runId);
		if (! StringUtils.equals(executionRun.getExecution().getName(), name)) {
			throw new NotFoundElement("No run " + runId + " for execution " + name);
		}
		return mapper.map(service.delete(executionRun));
	}

	@Override
	public SortedMap<LocalDate, List<ObservationDTO>> executionRuns(String name, Integer days, LocalDate from, LocalDate to, Map<String, Set<String>> keys) {
		Execution execution = service.getExecution(name);
		if (execution == null) {
			throw new NotFoundElement("No execution " + name);
		}

		if (days != null) {
			LocalDate startDate = LocalDate.now();
			if (to != null) {
				startDate = to;
			} else {
				to = startDate;
			}
			startDate = startDate.minusDays(days);
			from = startDate;
		}

		return mapExecutionObservations(service.calculateRunDiff(execution, from, to, keys));
	}

	@Override
	public SortedMap<LocalDate, List<ObservationDTO>> executionRunsObservations(String name, ExecutionRunsRequestDTO runsRequest) {
		Execution execution = service.getExecution(name);
		if (execution == null) {
			throw new NotFoundElement("No execution " + name);
		}
		return mapExecutionObservations(service.findExecutionRuns(execution, mapper.map(runsRequest)));
	}

	@Override
	public List<GroupedRunObservationsDTO> getObservationsGroupedByDimension(String name, String dimensionName, LocalDate toDate, Integer maxRuns) {
		Execution execution = service.getExecution(name);
		if (execution == null) {
			throw new NotFoundElement("No execution " + name);
		}
		return mapper.map(service.getObservationsGroupedByDimension(execution, dimensionName, toDate, maxRuns));
	}

	private SortedMap<LocalDate, List<ObservationDTO>> mapExecutionObservations(SortedMap<LocalDate, List<Observation>> sortedMap) {
		return sortedMap.entrySet()
			.stream()
			.collect(Collectors.toMap(
				Map.Entry::getKey,
				e -> mapper.map(e.getValue(), mapper::map),
				(e1, e2) -> e1,
				TreeMap::new
			));
	}

}