All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.castled.services.PipelineService Maven / Gradle / Ivy

package io.castled.services;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.castled.ObjectRegistry;
import io.castled.apps.ExternalApp;
import io.castled.apps.ExternalAppConnector;
import io.castled.apps.ExternalAppService;
import io.castled.apps.ExternalAppType;
import io.castled.apps.connectors.restapi.InvalidTemplateException;
import io.castled.apps.connectors.restapi.RestApiAppSyncConfig;
import io.castled.apps.connectors.restapi.RestApiTemplateParser;
import io.castled.apps.dtos.AppSyncConfigDTO;
import io.castled.caches.PipelineCache;
import io.castled.commons.models.PipelineSyncStats;
import io.castled.constants.CommonConstants;
import io.castled.daos.ErrorReportsDAO;
import io.castled.daos.PipelineDAO;
import io.castled.daos.PipelineRunDAO;
import io.castled.daos.QueryModelDAO;
import io.castled.dtos.*;
import io.castled.errors.PipelineErrorAndSample;
import io.castled.errors.PipelineRunErrors;
import io.castled.events.CastledEventType;
import io.castled.events.CastledEventsClient;
import io.castled.events.pipelineevents.PipelineEvent;
import io.castled.events.pipelineevents.PipelineEventType;
import io.castled.events.pipelineevents.PipelineRunEvent;
import io.castled.exceptions.CastledRuntimeException;
import io.castled.jarvis.JarvisTaskGroup;
import io.castled.jarvis.JarvisTaskType;
import io.castled.jarvis.taskmanager.JarvisTasksClient;
import io.castled.jarvis.taskmanager.models.RetryCriteria;
import io.castled.jarvis.taskmanager.models.requests.TaskCreateRequest;
import io.castled.misc.PipelineScheduleManager;
import io.castled.models.*;
import io.castled.models.users.User;
import io.castled.pubsub.MessagePublisher;
import io.castled.pubsub.registry.PipelineUpdatedMessage;
import io.castled.resources.validators.ResourceAccessController;
import io.castled.schema.SchemaUtils;
import io.castled.schema.mapping.MappingGroup;
import io.castled.schema.models.RecordSchema;
import io.castled.utils.JsonUtils;
import io.castled.utils.TimeUtils;
import io.castled.warehouses.WarehouseConnector;
import io.castled.warehouses.WarehouseService;
import io.castled.warehouses.WarehouseType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.QuoteMode;
import org.jdbi.v3.core.Jdbi;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j
@Singleton
@SuppressWarnings({"rawtypes", "unchecked"})
public class PipelineService {

    //just to make sure uuid starts with a character
    private static final String UUID_PREFIX = "c";
    private static final String ERROR_CODE = "__castled__error_code";
    private static final String ERROR_MESSAGE = "__castled__error_message";
    private static final String ERROR_RECORD_COUNT = "__castled__record_count";
    private final PipelineDAO pipelineDAO;
    private final PipelineCache pipelineCache;
    private final PipelineRunDAO pipelineRunDAO;
    private final CastledEventsClient castledEventsClient;
    private final Map warehouseConnectors;
    private final Map appConnectors;
    private final WarehouseService warehouseService;
    private final ExternalAppService externalAppService;
    private final ErrorReportsDAO errorReportsDAO;
    private final MessagePublisher messagePublisher;
    private final ResourceAccessController resourceAccessController;
    private final QueryModelDAO queryModelDAO;


    @Inject
    public PipelineService(Jdbi jdbi, CastledEventsClient castledEventsClient,
                           Map warehouseConnectors,
                           WarehouseService warehouseService,
                           PipelineCache pipelineCache, ExternalAppService externalAppService,
                           ResourceAccessController resourceAccessController, MessagePublisher messagePublisher,
                           Map appConnectors) {
        this.pipelineDAO = jdbi.onDemand(PipelineDAO.class);
        this.pipelineRunDAO = jdbi.onDemand(PipelineRunDAO.class);
        this.errorReportsDAO = jdbi.onDemand(ErrorReportsDAO.class);
        this.castledEventsClient = castledEventsClient;
        this.warehouseConnectors = warehouseConnectors;
        this.warehouseService = warehouseService;
        this.pipelineCache = pipelineCache;
        this.externalAppService = externalAppService;
        this.resourceAccessController = resourceAccessController;
        this.messagePublisher = messagePublisher;
        this.appConnectors = appConnectors;
        this.queryModelDAO = jdbi.onDemand(QueryModelDAO.class);
    }

    public Long createPipeline(PipelineConfigDTO pipelineConfigDTO, User user) {

        try {
            ExternalApp externalApp = this.externalAppService.getExternalApp(pipelineConfigDTO.getAppId(), true);
            PipelineConfigDTO enrichedPipelineConfig = this.appConnectors.get(externalApp.getType()).validateAndEnrichPipelineConfig(pipelineConfigDTO);
            //validPipelineConfig(enrichedPipelineConfig);
            Long pipelineId = this.pipelineDAO.createPipeline(enrichedPipelineConfig, user,
                    UUID_PREFIX + UUID.randomUUID().toString().replaceAll("-", "_"));
            this.castledEventsClient.publishPipelineEvent(new PipelineEvent(pipelineId, PipelineEventType.PIPELINE_CREATED));
            return pipelineId;
        } catch (ClientErrorException e) {
            log.warn("Create pipeline failed for app {} and warehouse {}", pipelineConfigDTO.getAppId(),
                    pipelineConfigDTO.getWarehouseId(), e);
            throw e;
        } catch (Exception e) {
            log.warn("Create pipeline failed for app {} and warehouse {}", pipelineConfigDTO.getAppId(),
                    pipelineConfigDTO.getWarehouseId(), e);
            throw new CastledRuntimeException(e);
        }
    }

    public void updatePipeline(Long pipelineId, PipelineUpdateRequest pipelineUpdateRequest) {
        this.pipelineDAO.updatePipeline(pipelineId, pipelineUpdateRequest.getName(), pipelineUpdateRequest.getSchedule(), pipelineUpdateRequest.getQueryMode());
    }

    public void triggerPipeline(long pipelineId, long teamId) {
        Pipeline pipeline = getActivePipeline(pipelineId);
        resourceAccessController.validatePipelineAccess(pipeline, teamId);
        doTriggerPipeline(pipeline);

    }

    private void doTriggerPipeline(Pipeline pipeline) {
        try {
            TaskCreateRequest taskCreateRequest = TaskCreateRequest.builder()
                    .group(JarvisTaskGroup.PIPELINE_RUN.name())
                    .type(JarvisTaskType.PIPELINE_RUN.name())
                    .expiry(Math.max(TimeUtils.secondsToMillis(pipeline.getJobSchedule().getExecutionTime()),
                            TimeUtils.minutesToMillis(120)))
                    .params(ImmutableMap.of(CommonConstants.PIPELINE_ID, pipeline.getId()))
                    .uniqueId(String.valueOf(pipeline.getId())).retryCriteria(new RetryCriteria(3, true))
                    .build();
            ObjectRegistry.getInstance(JarvisTasksClient.class).createTask(taskCreateRequest);
        } catch (Exception e) {
            log.error("Trigger pipeline {} failed", pipeline.getId());
            throw new CastledRuntimeException(e);
        }

    }

    public void restartPipeline(Long pipelineId, Long teamId) throws Exception {
        Pipeline pipeline = getActivePipeline(pipelineId, true);
        this.resourceAccessController.validatePipelineAccess(pipeline, teamId);
        Warehouse warehouse = warehouseService.getWarehouse(pipeline.getWarehouseId(), true);
        this.warehouseConnectors.get(warehouse.getType()).restartPoll(pipeline.getUuid(), warehouse.getConfig());
        doTriggerPipeline(pipeline);
    }

    public Pipeline getActivePipeline(Long pipelineId, boolean cached) {
        if (cached) {
            return pipelineCache.getValue(pipelineId);
        }
        return this.pipelineDAO.getActivePipeline(pipelineId);
    }

    public void updatePipelineRunstage(Long pipelineRunId, PipelineRunStage stage) {
        this.pipelineRunDAO.updatePipelineRunStage(pipelineRunId, stage);
    }

    public void deletePipeline(Long pipelineId, Long teamId) {
        Pipeline pipeline = getActivePipeline(pipelineId, true);
        this.resourceAccessController.validatePipelineAccess(pipeline, teamId);
        this.pipelineDAO.markPipelineDeleted(pipelineId);
        this.castledEventsClient.publishPipelineEvent(new PipelineEvent(pipelineId, PipelineEventType.PIPELINE_DELETED));
    }

    public void pausePipeline(long pipelineId, Long teamId) {
        Pipeline pipeline = getActivePipeline(pipelineId, true);
        this.resourceAccessController.validatePipelineAccess(pipeline, teamId);
        ObjectRegistry.getInstance(PipelineScheduleManager.class).unschedulePipeline(pipelineId);
        this.pipelineDAO.updateSyncStatus(pipelineId, PipelineSyncStatus.PAUSED);
        this.messagePublisher.publishMessage(new PipelineUpdatedMessage(pipelineId));
    }

    public void resumePipeline(long pipelineId, Long teamId) {
        Pipeline pipeline = getActivePipeline(pipelineId, true);
        this.resourceAccessController.validatePipelineAccess(pipeline, teamId);
        ObjectRegistry.getInstance(PipelineScheduleManager.class).reschedulePipeline(pipelineId);
        this.pipelineDAO.updateSyncStatus(pipelineId, PipelineSyncStatus.ACTIVE);
        this.messagePublisher.publishMessage(new PipelineUpdatedMessage(pipelineId));
    }

    public Pipeline getActivePipeline(Long pipelineId) {
        return getActivePipeline(pipelineId, false);
    }

    public long createPipelineRun(Long pipelineId) {
        return this.pipelineRunDAO.createPipelineRun(pipelineId,
                new PipelineSyncStats(0, 0, 0, 0));
    }

    public void markPipelineRunProcessed(Long pipelineRunId, Long pipelineId, PipelineSyncStats pipelineSyncStats) {
        this.pipelineRunDAO.markProcessed(pipelineRunId, pipelineSyncStats);
        this.castledEventsClient.publishCastledEvent(new PipelineRunEvent(pipelineId, pipelineRunId, CastledEventType.PIPELINE_RUN_COMPLETED));
    }

    public void markPipelineRunFailed(Long pipelineRunId, Long pipelineId, String failureMessage) {
        this.pipelineRunDAO.markFailed(pipelineRunId, failureMessage);
        this.castledEventsClient.publishCastledEvent(new PipelineRunEvent(pipelineId, pipelineRunId, CastledEventType.PIPELINE_RUN_FAILED));
    }

    public void updateSyncStats(Long pipelineRunId, PipelineSyncStats pipelineSyncStats) {
        this.pipelineRunDAO.updateSyncStatus(pipelineRunId, pipelineSyncStats);
    }

    public PipelineRun getPipelineRun(Long pipelineRunId) {
        return pipelineRunDAO.getPipelineRun(pipelineRunId);
    }

    public List getPipelineRuns(Long pipelineId, int limit) {
        if (limit == 0) {
            return pipelineRunDAO.getLastPipelineRuns(pipelineId, 100);
        }
        return pipelineRunDAO.getLastPipelineRuns(pipelineId, limit);
    }

    public PipelineRun getLastRun(Long pipelineId) {
        List pipelineRuns = getPipelineRuns(pipelineId, 1);
        if (CollectionUtils.isEmpty(pipelineRuns)) {
            return null;
        }
        return pipelineRuns.get(0);
    }

    public PipelineRunErrors getPipelineRunErrors(Long pipelineRunId) {
        ErrorReport errorReport = errorReportsDAO.getErrorReport(pipelineRunId);
        if (errorReport == null) {
            return new PipelineRunErrors(Lists.newArrayList(), Lists.newArrayList());
        }

        List pipelineErrorAndSamples = Lists.newArrayList();
        String[] reportTokens = errorReport.getReport().split(System.lineSeparator());
        List sampleFields = JsonUtils.jsonStringToTypeReference(reportTokens[0], new TypeReference>() {
        });
        for (String error : Arrays.asList(reportTokens).subList(1, reportTokens.length)) {
            pipelineErrorAndSamples.add(JsonUtils.jsonStringToObject(error, PipelineErrorAndSample.class));
        }
        return new PipelineRunErrors(sampleFields, pipelineErrorAndSamples);
    }

    public StreamingOutput downloadErrorReport(Long pipelineRunId) throws IOException {

        PipelineRunErrors pipelineRunErrors = getPipelineRunErrors(pipelineRunId);
        List errorReportFields = getErrorReportFields(pipelineRunErrors.getSampleFields());
        StringBuffer errorBuffer = new StringBuffer();


        CSVPrinter csvPrinter = new CSVPrinter(errorBuffer, CSVFormat.DEFAULT
                .withHeader(errorReportFields.toArray(new String[0])).withQuoteMode(QuoteMode.ALL));
        for (PipelineErrorAndSample pipelineErrorAndSample : pipelineRunErrors.getErrorAndSamples()) {
            List fieldValues = Lists.newArrayList();
            for (String sampleField : pipelineRunErrors.getSampleFields()) {
                fieldValues.add(Optional.ofNullable(pipelineErrorAndSample.getRecord().get(sampleField))
                        .map(Object::toString).orElse("N/A"));

            }
            fieldValues.add(pipelineErrorAndSample.getErrorCode().name());
            fieldValues.add(pipelineErrorAndSample.getDescription());
            fieldValues.add(String.valueOf(pipelineErrorAndSample.getRecordCount()));
            csvPrinter.printRecord(fieldValues);
        }
        csvPrinter.flush();
        csvPrinter.close();

        return outputStream -> {
            outputStream.write(errorBuffer.toString().getBytes());
            outputStream.flush();
        };
    }

    public Pipeline getPipeline(Long pipelineId) {
        return pipelineDAO.getPipeline(pipelineId);
    }

    private List getErrorReportFields(List sampleFields) {
        List reportFields = Lists.newArrayList(sampleFields);
        reportFields.add(ERROR_CODE);
        reportFields.add(ERROR_MESSAGE);
        reportFields.add(ERROR_RECORD_COUNT);
        return reportFields;
    }

    public void triggerDummyRun() {
        try {
            TaskCreateRequest taskCreateRequest = TaskCreateRequest.builder()
                    .type(JarvisTaskType.DUMMY.name())
                    .group(JarvisTaskGroup.OTHERS.name())
                    .expiry(TimeUtils.minutesToMillis(1345))
                    .retryCriteria(new RetryCriteria(3, true))
                    .build();
            ObjectRegistry.getInstance(JarvisTasksClient.class).createTask(taskCreateRequest);
        } catch (Exception e) {
            log.error("Trigger dummy run failed", e);
            throw new CastledRuntimeException(e);
        }
    }

    public PipelineSchema getPipelineSchema(AppSyncConfigDTO appSyncConfigDTO) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(2,
                new ThreadFactoryBuilder().setNameFormat("pipeline-schema-fetch-%d").build());
        try {
            Future warehouseSchema = executorService.submit(() -> warehouseService.fetchSchema(appSyncConfigDTO.getWarehouseId(), appSyncConfigDTO.getSourceQuery()));
            Future> mappingGroups = executorService.submit(() -> externalAppService.getMappingGroup(appSyncConfigDTO.getAppId(), appSyncConfigDTO.getAppSyncConfig()));

            return new PipelineSchema(SchemaUtils.transformToSimpleSchema(enrichWarehouseSchema(appSyncConfigDTO, warehouseSchema)),
                    mappingGroups.get());
        } finally {
            executorService.shutdownNow();
        }
    }

    private RecordSchema enrichWarehouseSchema(AppSyncConfigDTO appSyncConfigDTO, Future warehouseSchema) throws ExecutionException, InterruptedException {
        ExternalApp externalApp = this.externalAppService.getExternalApp(appSyncConfigDTO.getAppId(), true);
        return this.appConnectors.get(externalApp.getType()).enrichWarehouseASchema(appSyncConfigDTO, warehouseSchema.get());
    }

    public List listPipelines(Long teamid, Long appId) {
        if (appId == null) {
            return pipelineDAO.listPipelines(teamid);
        }
        return pipelineDAO.listPipelines(teamid, appId);
    }

    public void testDataMapping(MappingTestRequest mappingTestRequest) {
        if (mappingTestRequest.getMapping().getType() == DataMappingType.TARGET_FIELDS_MAPPING) {
            return;
        }
        try {
            RestApiTemplateParser restApiTemplateParser = new RestApiTemplateParser();
            TargetRestApiMapping targetRestApiMapping = (TargetRestApiMapping) mappingTestRequest.getMapping();
            String template = targetRestApiMapping.getTemplate();
            restApiTemplateParser.validateMustacheJson(template, (RestApiAppSyncConfig) mappingTestRequest.getAppSyncConfig());
            List templateFields = restApiTemplateParser.getTemplateVariables(template);
            List invalidFields = ListUtils.subtract(templateFields, mappingTestRequest.getQueryFields());
            if (CollectionUtils.isNotEmpty(invalidFields)) {
                throw new BadRequestException(String.format("Fields %s not present in the selected query",
                        invalidFields));
            }
        } catch (InvalidTemplateException e) {
            throw new BadRequestException(e.getMessage());
        }
    }

    public List getWarehouseAggregates(Long teamId) {
        return pipelineDAO.aggregateByWarehouse(teamId);
    }

    public int getWarehousePipelines(Long warehouseId) {
        return pipelineDAO.getWarehousePipelines(warehouseId);
    }

    public int getAppPipelines(Long appId) {
        return pipelineDAO.getAppPipelines(appId);
    }

    public List getAppAggregates(Long teamId) {
        return pipelineDAO.aggregateByApp(teamId);
    }

    public List getModelAggregates(Long teamId, List modelIds) {
        if (CollectionUtils.isNotEmpty(modelIds)) {
            return pipelineDAO.aggregateByModel(teamId, modelIds);
        }
        return pipelineDAO.aggregateByModel(teamId);
    }

    public int pipelineCountUsingModel(Long modelId) {
        return this.pipelineDAO.pipelineCountUsingModel(modelId);
    }

    public List listPipelinesByModelId(Long teamid, Long modelId) {
        if (modelId == null) {
            return pipelineDAO.listPipelines(teamid);
        }
        return pipelineDAO.listPipelinesByModelId(teamid, modelId);
    }

    public ConsolidatedCountDTO getAllCountDetails(Long teamId) {
        int pipelines;
        int demoPipelines = 0;
        QueryModel demoModel = queryModelDAO.getDemoModelForTeam(teamId);
        if (demoModel != null) {
            pipelines = pipelineDAO.getAllPipelinesCreatedUsingOwnWarehouse(teamId, demoModel.getId());
            demoPipelines = pipelineDAO.getAllPipelinesCreatedUsingDemoWarehouse(teamId, demoModel.getId());
        } else {
            pipelines = pipelineDAO.getAllPipelinesCreatedUsingOwnWarehouse(teamId);
        }

        int models = queryModelDAO.getTotalActiveModelsForTeam(teamId);
        int warehouses = warehouseService.getTotalActiveWarehousesForTeam(teamId);
        int apps = externalAppService.getTotalActiveAppsForTeam(teamId);

        return ConsolidatedCountDTO.builder().pipelines(pipelines).demoPipelines(demoPipelines).apps(apps).models(models).warehouses(warehouses).build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy