/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.common.util;

import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;

import javax.ws.rs.core.MediaType;

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Jakub Bartecek <jbartece@redhat.com>
 */
public class HttpUtils {

    private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);

    private HttpUtils() {
    }

    /**
     * Process HTTP GET request and get the data as type specified as parameter.
     * Client accepts application/json MIME type.
     *
     * @param clazz Class to which the data are unmarshalled
     * @param <T> module config
     * @param url Request URL
     * @throws Exception Thrown if some error occurs in communication with server
     * @return Unmarshalled entity data
     */
    public static <T> T processGetRequest(Class<T> clazz, String url) throws Exception {
        ClientRequest request = new ClientRequest(url);
        request.accept(MediaType.APPLICATION_JSON);

        ClientResponse<T> response = request.get(clazz);
        return response.getEntity();
    }

    public static String processGetRequest(String url) throws Exception {
        ClientRequest request = new ClientRequest(url);
        request.accept(MediaType.APPLICATION_JSON);
        ClientResponse<String> response = request.get(String.class);
        return response.getEntity();
    }

    /**
     * Process HTTP requests and tests if server responds with expected HTTP code.
     * Request is implicitly set to accept MIME type application/json.
     *
     * @param ecode Expected HTTP error code
     * @param url Request URL
     * @throws Exception Thrown if some error occurs in communication with server
     */
    public static void testResponseHttpCode(int ecode, String url) throws Exception {
        ClientRequest request = new ClientRequest(url);
        request.accept(MediaType.APPLICATION_JSON);

        ClientResponse<String> response = request.get(String.class);
        if (response.getStatus() != ecode)
            throw new Exception("Server returned unexpected HTTP code! Returned code:" + response.getStatus());
    }

    /**
     *
     * NOTE: Be sure to close the HTTP connection after every request!
     * @param retries - int number of retries to execute request in case of failure
     * @return Closeable "permissive" HttpClient instance, ignoring invalid SSL certificates.
     */
    public static CloseableHttpClient getPermissiveHttpClient(int retries) {
        SSLContextBuilder builder = new SSLContextBuilder();
        try {
            builder.loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            });
        } catch (NoSuchAlgorithmException | KeyStoreException e1) {
            LOG.error("Error creating SSL Context Builder with trusted certificates.", e1);
        }

        SSLConnectionSocketFactory sslSF = null;
        try {
            sslSF = new SSLConnectionSocketFactory(builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (KeyManagementException | NoSuchAlgorithmException e1) {
            LOG.error("Error creating SSL Connection Factory.", e1);
        }

        CloseableHttpClient httpclient = HttpClients.custom()
                .setRetryHandler(new DefaultHttpRequestRetryHandler(retries, false))
                .setSSLSocketFactory(sslSF)
                .setHostnameVerifier(new AllowAllHostnameVerifier()).build();

        return httpclient;
    }

    /**
     * @return Closeable "permissive" HttpClient instance, ignoring invalid SSL certificates, using 3 attempts to retry failed request
     * @see getPermissiveHttpClient(int retries)
     */
    public static CloseableHttpClient getPermissiveHttpClient() {
        return getPermissiveHttpClient(3);
    }

    public static boolean isSuccess(int statusCode) {
        return statusCode / 100 == 2;
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.common.json;

import org.jboss.pnc.common.json.moduleprovider.ConfigProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

/**
 * @author Pavel Slegr
 * @author Jakub Bartecek <jbartece@redhat.com>
 * @author Honza Brazdil <jbrazdil@redhat.com>
 */
public class ConfigurationJSONParser {

    public final static Logger log = LoggerFactory.getLogger(ConfigurationJSONParser.class);

    /**
     * Loads JSON configuration to the module configuration object
     *
     * @param <T> module config
     * @param configContent Configuration in JSON
     * @param provider configuration provider of given module config type
     * @return Loaded configuration
     * @throws ConfigurationParseException Thrown if configuration string is malformed
     */
    public <T extends AbstractModuleConfig> T parseJSONPNCConfig(
            String configContent, ConfigProvider<T> provider) throws ConfigurationParseException {
        try {
            ObjectMapper mapper = new ObjectMapper();
            provider.registerProvider(mapper);

            PNCModuleGroup pncGroup = getModuleGroup(mapper, configContent,
                    PNCModuleGroup.class);

            for (AbstractModuleConfig config : pncGroup.getConfigs()) {
                if (config.getClass().isAssignableFrom(provider.getType())) {
                    return (T) config;
                }
            }
            throw new ConfigurationParseException("Did not find config for provider " + provider.getType().getSimpleName() + ".");
        } catch (IOException | RuntimeException e) {
            log.error(e.getMessage());
            throw new ConfigurationParseException("Config could not be parsed", e);
        }
    }

    public GlobalModuleGroup parseJSONGlobalConfig(String configContent) throws ConfigurationParseException {
        try {
            return getModuleGroup(new ObjectMapper(), configContent,
                    GlobalModuleGroup.class);
        } catch (IOException | RuntimeException e) {
            throw new ConfigurationParseException("Config could not be parsed", e);
        }
    }

    private <T> T getModuleGroup(ObjectMapper mapper, String configContent, Class<T> type) throws IOException, ConfigurationParseException {
        mapper.registerSubtypes(type);
        ModuleConfigJson jsonConfig = mapper.readValue(configContent, ModuleConfigJson.class);

        for (AbstractModuleGroup group : jsonConfig.getConfigs()) {
            if (group.getClass().isAssignableFrom(type)) {
                return (T) group;
            }
        }
        throw new ConfigurationParseException("Config group could not be parsed");
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.rest.endpoint;

import io.swagger.v3.oas.annotations.Hidden;
import org.jboss.pnc.auth.AuthenticationProvider;
import org.jboss.pnc.auth.AuthenticationProviderFactory;
import org.jboss.pnc.auth.LoggedInUser;
import org.jboss.pnc.bpm.BpmManager;
import org.jboss.pnc.bpm.BpmTask;
import org.jboss.pnc.bpm.task.BpmBuildTask;
import org.jboss.pnc.common.Configuration;
import org.jboss.pnc.common.json.moduleconfig.SystemConfig;
import org.jboss.pnc.common.json.moduleconfig.UIModuleConfig;
import org.jboss.pnc.common.json.moduleprovider.PncConfigProvider;
import org.jboss.pnc.common.logging.BuildTaskContext;
import org.jboss.pnc.common.logging.MDCUtils;
import org.jboss.pnc.rest.restmodel.BuildExecutionConfigurationRest;
import org.jboss.pnc.rest.restmodel.bpm.BuildResultRest;
import org.jboss.pnc.rest.restmodel.response.AcceptedResponse;
import org.jboss.pnc.rest.restmodel.response.Singleton;
import org.jboss.pnc.rest.trigger.BuildExecutorTriggerer;
import org.jboss.pnc.rest.utils.ErrorResponse;
import org.jboss.pnc.spi.coordinator.BuildTask;
import org.jboss.pnc.spi.exception.CoreException;
import org.jboss.pnc.spi.executor.BuildExecutionSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.util.Optional;

@Hidden
@Path("/build-tasks")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BuildTaskEndpoint {

    @Inject
    private BpmManager bpmManager;

    @Inject
    private BuildExecutorTriggerer buildExecutorTriggerer;

    @Inject
    private AuthenticationProviderFactory authenticationProviderFactory;

    @Inject
    Configuration configuration;

    @Inject
    SystemConfig systemConfig;

    private static final Logger logger = LoggerFactory.getLogger(BuildTaskEndpoint.class);

    @Deprecated
    public BuildTaskEndpoint() {} // CDI workaround

    @POST
    @Path("/{taskId}/completed")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response buildTaskCompleted(
            @PathParam("taskId") Integer buildId,
            @FormParam("buildResult") BuildResultRest buildResult) throws CoreException {

//TODO set MDC from request headers instead of business data
//        logger.debug("Received task completed notification for coordinating task id [{}].", buildId);
//        BuildExecutionConfigurationRest buildExecutionConfiguration = buildResult.getBuildExecutionConfiguration();
//        buildResult.getRepositoryManagerResult().getBuildContentId();
//        if (buildExecutionConfiguration == null) {
//            logger.error("Missing buildExecutionConfiguration in buildResult for buildTaskId [{}].", buildId);
//            throw new CoreException("Missing buildExecutionConfiguration in buildResult for buildTaskId " + buildId);
//        }
//        MDCUtils.addContext(buildExecutionConfiguration.getBuildContentId(), buildExecutionConfiguration.isTempBuild(), systemConfig.getTemporalBuildExpireDate());
        logger.info("Received build task completed notification for id {}.", buildId);

        Integer taskId = bpmManager.getTaskIdByBuildId(buildId);
        if(taskId == null) {
            logger.error("No task for id [{}].", buildId);
            throw new CoreException("Could not find BPM task for build with ID " + buildId);
        }

        //check if task is already completed
        //required workaround as we don't remove the BpmTasks immediately after the completion
        Optional<BpmTask> taskOptional = bpmManager.getTaskById(taskId);
        if (taskOptional.isPresent()) {
            BpmBuildTask bpmBuildTask = (BpmBuildTask) taskOptional.get();
            BuildTask buildTask = bpmBuildTask.getBuildTask();

            MDCUtils.addBuildContext(buildTask.getContentId(), buildTask.getBuildOptions().isTemporaryBuild(), systemConfig.getTemporalBuildExpireDate());
            if (buildTask.getStatus().isCompleted()) {
                logger.warn("Task with id: {} is already completed with status: {}", buildTask.getId(), buildTask.getStatus());
                return Response.status(Response.Status.GONE).entity("Task with id: " + buildTask.getId() + " is already completed with status: " + buildTask.getStatus() + ".").build();
            }
        }

        if (logger.isTraceEnabled()) {
            logger.trace("Received build result wit full log: {}.", buildResult.toFullLogString());
        }
        logger.debug("Will notify for bpmTaskId[{}] linked to buildTaskId [{}].", taskId, buildId);
        bpmManager.notify(taskId, buildResult);
        logger.debug("Notified for bpmTaskId[{}] linked to buildTaskId [{}].", taskId, buildId);
        return Response.ok().build();
    }

    @POST
    @Path("/execute-build")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED) //TODO accept single json
    public Response build(

            @FormParam("buildExecutionConfiguration")
            BuildExecutionConfigurationRest buildExecutionConfiguration,

            @FormParam("usernameTriggered")
            String usernameTriggered,

            @FormParam("callbackUrl")
            String callbackUrl,
            @Context UriInfo uriInfo,
            @Context HttpServletRequest request) {

        try {
            logger.debug("Endpoint /execute-build requested for buildTaskId [{}], from [{}]", buildExecutionConfiguration.getId(), request.getRemoteAddr());
            MDCUtils.addBuildContext(buildExecutionConfiguration.getBuildContentId(), buildExecutionConfiguration.isTempBuild(), systemConfig.getTemporalBuildExpireDate());
            logger.info("Build execution requested.");
            logger.debug("Staring new build execution for configuration: {}. Caller requested a callback to {}.", buildExecutionConfiguration.toString(), callbackUrl);

            AuthenticationProvider authenticationProvider = authenticationProviderFactory.getProvider();
            LoggedInUser loginInUser = authenticationProvider.getLoggedInUser(request);

            BuildExecutionSession buildExecutionSession = buildExecutorTriggerer.executeBuild(
                    buildExecutionConfiguration.toBuildExecutionConfiguration(),
                    callbackUrl,
                    loginInUser.getTokenString());

            UIModuleConfig uiModuleConfig = configuration.getModuleConfig(new PncConfigProvider<>(UIModuleConfig.class));
            UriBuilder uriBuilder = UriBuilder.fromUri(uiModuleConfig.getPncUrl()).path("/ws/executor/notifications");

            String id = Integer.toString(buildExecutionConfiguration.getId());
            AcceptedResponse acceptedResponse = new AcceptedResponse(id, uriBuilder.build().toString());

            Response response = Response.ok().entity(new Singleton(acceptedResponse)).build();
            return response;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ErrorResponse.toResponse(e);
        }
    }

    @POST
    @Path("/cancel-build/{buildExecutionConfigurationId}")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response cancelBbuild(

            @PathParam("buildExecutionConfigurationId")
            Integer buildExecutionConfigurationId,
            @Context UriInfo uriInfo,
            @Context HttpServletRequest request) {
        logger.debug("Endpoint /cancel-build requested for buildTaskId [{}], from [{}]", buildExecutionConfigurationId, request.getRemoteAddr());
        try {
            Optional<BuildTaskContext> mdcMeta = buildExecutorTriggerer.getMdcMeta(buildExecutionConfigurationId);
            if (mdcMeta.isPresent()) {
                MDCUtils.addContext(mdcMeta.get());
            } else {
                logger.warn("Unable to retrieve MDC meta. There is no running build for buildExecutionConfigurationId: {}.", buildExecutionConfigurationId);
            }

            logger.info("Cancelling build execution for configuration.id: {}.", buildExecutionConfigurationId);
            buildExecutorTriggerer.cancelBuild(buildExecutionConfigurationId);

            Response response = Response.ok().build();
            return response;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ErrorResponse.toResponse(e);
        }
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.indyrepositorymanager;

import org.commonjava.indy.client.core.Indy;
import org.commonjava.indy.client.core.IndyClientException;
import org.commonjava.indy.model.core.StoreKey;
import org.commonjava.indy.model.core.StoreType;
import org.jboss.pnc.spi.repositorymanager.model.CompletedRepositoryDeletion;
import org.jboss.pnc.spi.repositorymanager.model.RunningRepositoryDeletion;

import java.util.function.Consumer;

public class IndyRunningDeletion implements RunningRepositoryDeletion {

    private String pakageType;
    private StoreType fromType;
    private String fromId;
    private Indy indy;

    public IndyRunningDeletion(String pakageType, StoreType fromType, String fromId, Indy indy) {
        this.pakageType = pakageType;
        this.fromType = fromType;
        this.fromId = fromId;
        this.indy = indy;
    }

    /**
     * Trigger the repository deletion configured for this instance, and send the result to the appropriate consumer.
     *
     * @param onComplete The consumer which will handle non-error results
     * @param onError Handles errors
     */
    @Override
    public void monitor(Consumer<CompletedRepositoryDeletion> onComplete, Consumer<Exception> onError) {
        try {
            StoreKey fromKey = new StoreKey(pakageType, fromType, fromId);
            if (indy.stores().exists(fromKey)) {
                indy.stores().delete(fromKey, "Deleting artifacts for PNC build");
            }

            onComplete.accept(new IndyCompletedDeletion(true));

        } catch (IndyClientException e) {
            onError.accept(e);
        }
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.demo.data;

import com.google.common.base.Preconditions;
import org.jboss.pnc.common.json.moduleconfig.DemoDataConfig;
import org.jboss.pnc.common.json.moduleconfig.SystemConfig;
import org.jboss.pnc.model.Artifact;
import org.jboss.pnc.enums.BuildType;
import org.jboss.pnc.model.TargetRepository;
import org.jboss.pnc.model.BuildConfigSetRecord;
import org.jboss.pnc.model.BuildConfiguration;
import org.jboss.pnc.model.BuildConfigurationAudited;
import org.jboss.pnc.model.BuildConfigurationSet;
import org.jboss.pnc.model.BuildEnvironment;
import org.jboss.pnc.model.BuildRecord;
import org.jboss.pnc.enums.BuildStatus;
import org.jboss.pnc.model.IdRev;
import org.jboss.pnc.model.Product;
import org.jboss.pnc.model.ProductMilestone;
import org.jboss.pnc.model.ProductRelease;
import org.jboss.pnc.enums.SupportLevel;
import org.jboss.pnc.model.ProductVersion;
import org.jboss.pnc.model.Project;
import org.jboss.pnc.model.RepositoryConfiguration;
import org.jboss.pnc.enums.SystemImageType;
import org.jboss.pnc.model.User;
import org.jboss.pnc.spi.datastore.Datastore;
import org.jboss.pnc.spi.datastore.repositories.ArtifactRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildConfigSetRecordRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildConfigurationAuditedRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildConfigurationRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildConfigurationSetRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildEnvironmentRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildRecordRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductMilestoneRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductReleaseRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductVersionRepository;
import org.jboss.pnc.spi.datastore.repositories.ProjectRepository;
import org.jboss.pnc.spi.datastore.repositories.RepositoryConfigurationRepository;
import org.jboss.pnc.spi.datastore.repositories.SequenceHandlerRepository;
import org.jboss.pnc.spi.datastore.repositories.TargetRepositoryRepository;
import org.jboss.pnc.spi.datastore.repositories.UserRepository;

import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;

import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.jboss.pnc.enums.RepositoryType;
import static org.jboss.pnc.spi.datastore.predicates.ArtifactPredicates.withIdentifierAndSha256;

/**
 * Data for the DEMO. Note: The database initialization requires two separate transactions in order for the build configuration
 * audit record to be created and then linked to a build record.
 */
@Singleton
public class DatabaseDataInitializer {

    public static final Logger log = Logger.getLogger(DatabaseDataInitializer.class.getName());

    private static final String PNC_PRODUCT_NAME = "Project Newcastle Demo Product";

    private static final String PNC_PRODUCT_VERSION_1 = "1.0";

    private static final String PNC_PRODUCT_VERSION_2 = "2.0";

    private static final String PNC_PRODUCT_RELEASE = "1.0.0.GA";

    private static final String PNC_PRODUCT_MILESTONE1 = "1.0.0.Build1";

    private static final String PNC_PRODUCT_MILESTONE2 = "1.0.0.Build2";

    private static final String PNC_PROJECT_1_NAME = "Project Newcastle Demo Project 1";

    private static final String PNC_PROJECT_BUILD_CFG_ID = "pnc-1.0.0.DR1";

    @Inject
    private ArtifactRepository artifactRepository;

    @Inject
    private TargetRepositoryRepository targetRepositoryRepository;

    @Inject
    private ProjectRepository projectRepository;

    @Inject
    private ProductRepository productRepository;

    @Inject
    private RepositoryConfigurationRepository repositoryConfigurationRepository;

    @Inject
    private BuildConfigurationRepository buildConfigurationRepository;

    @Inject
    BuildConfigurationAuditedRepository buildConfigurationAuditedRepository;

    @Inject
    private ProductVersionRepository productVersionRepository;

    @Inject
    private ProductMilestoneRepository productMilestoneRepository;

    @Inject
    private ProductReleaseRepository productReleaseRepository;

    @Inject
    private BuildConfigurationSetRepository buildConfigurationSetRepository;

    @Inject
    private UserRepository userRepository;

    @Inject
    private BuildRecordRepository buildRecordRepository;

    @Inject
    private BuildConfigSetRecordRepository buildConfigSetRecordRepository;

    @Inject
    private BuildEnvironmentRepository environmentRepository;

    @Inject
    private SequenceHandlerRepository sequenceHandlerRepository;

    @Inject
    private Datastore datastore;

    @Inject
    DemoDataConfig demoDataConfig;

    @Inject
    SystemConfig systemConfig;

    BuildConfiguration buildConfiguration1;

    BuildConfiguration buildConfiguration2;

    BuildConfigurationSet buildConfigurationSet1;

    ProductMilestone demoProductMilestone1;

    User demoUser;

    User pncAdminUser;

    public void verifyData() {
        // Check number of entities in DB
        Preconditions.checkState(projectRepository.count() > 0, "Expecting number of Projects > 0");
        Preconditions.checkState(productRepository.count() > 0, "Expecting number of Products > 0");
        Preconditions.checkState(buildConfigurationRepository.count() > 0,
                "Expecting number of BuildConfigurations > 0");
        Preconditions.checkState(productVersionRepository.count() > 0,
                "Expecting number of ProductVersions > 0");
        Preconditions.checkState(buildConfigurationSetRepository.count() > 0,
                "Expecting number of BuildRepositorySets > 0");
        Preconditions.checkState(artifactRepository.count() > 0,
                "Expecting number of Artifacts > 0");

        BuildConfiguration buildConfigurationDB = buildConfigurationRepository.queryAll().get(0);

        // Check that BuildConfiguration and BuildConfigurationSet have a ProductVersion associated
        BuildConfigurationSet buildConfigurationSet = buildConfigurationDB.getBuildConfigurationSets().iterator().next();
        Preconditions.checkState(
                buildConfigurationSet
                        .getProductVersion() != null,
                "Product version of buildConfiguration must be not null");

        BuildConfigurationSet buildConfigurationSetDB = buildConfigurationSetRepository.queryAll()
                .get(0);

        Preconditions.checkState(buildConfigurationSetDB.getProductVersion() != null,
                "Product version of buildConfigurationSet must be not null");

        // Check that mapping between Product and Build Configuration via BuildConfigurationSet is correct
        Preconditions.checkState(buildConfigurationSetDB.getProductVersion().getProduct().getName()
                .equals(PNC_PRODUCT_NAME),
                "Product mapped to Project must be " + PNC_PRODUCT_NAME);
        Preconditions.checkState(
                buildConfigurationSetDB.getProductVersion().getVersion()
                        .equals(PNC_PRODUCT_VERSION_1),
                "Product version mapped to Project must be " + PNC_PRODUCT_VERSION_1);

        // Check that BuildConfiguration and BuildConfigurationSet have a ProductVersion associated
        Preconditions.checkState(buildConfigurationSet
                .getProductVersion()
                .getVersion().equals(PNC_PRODUCT_VERSION_1),
                "Product version mapped to BuildConfiguration must be "
                        + PNC_PRODUCT_VERSION_1);
        Preconditions.checkState(buildConfigurationSet
                .getProductVersion()
                .getProduct().getName().equals(PNC_PRODUCT_NAME),
                "Product mapped to BuildConfiguration must be "
                        + PNC_PRODUCT_NAME);

        // Check data of BuildConfiguration
        Preconditions.checkState(
                buildConfigurationDB.getProject().getName().equals(PNC_PROJECT_1_NAME),
                "Project mapped to BuildConfiguration must be " + PNC_PROJECT_1_NAME);

    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void initiliazeProjectProductData() {

        BuildEnvironment environment1Unsaved = BuildEnvironment.Builder.newBuilder()
                .name("Demo Environment 1")
                .description("Basic Java and Maven Environment")
                .attribute("JDK", "1.7.0")
                .attribute("OS", "Linux")
                .systemImageId("12345678")
                .systemImageRepositoryUrl("my.registry/newcastle")
                .systemImageType(SystemImageType.DOCKER_IMAGE)
                .deprecated(false)
                .build();
        BuildEnvironment environment1 = environmentRepository.save(environment1Unsaved);

        BuildEnvironment environment2Unsaved = BuildEnvironment.Builder.newBuilder()
                .name("Demo Environment 2")
                .description("Basic Java and Maven Environment")
                .attribute("JDK", "1.7.0")
                .attribute("OS", "Linux")
                .systemImageId("12345679")
                .systemImageRepositoryUrl("my.registry/newcastle")
                .systemImageType(SystemImageType.DOCKER_IMAGE)
                .deprecated(true)
                .build();
        BuildEnvironment environment2 = environmentRepository.save(environment2Unsaved);

        /*
         * All the bi-directional mapping settings are managed inside the Builders
         */
        // Example product and product version
        Product product = Product.Builder.newBuilder().name(PNC_PRODUCT_NAME).abbreviation("PNC")
                .description("Example Product for Project Newcastle Demo").productCode("PNC")
                .pgmSystemName("newcastle")
                .build();
        product = productRepository.save(product);

        // Example product version, release, and milestone of the product
        ProductVersion productVersion1 = ProductVersion.Builder.newBuilder()
                .version(PNC_PRODUCT_VERSION_1).product(product)
                .generateBrewTagPrefix(product.getAbbreviation(), PNC_PRODUCT_VERSION_1, systemConfig.getBrewTagPattern())
                .build();
        productVersion1 = productVersionRepository.save(productVersion1);

        ProductVersion productVersion2 = ProductVersion.Builder.newBuilder()
                .version(PNC_PRODUCT_VERSION_2).product(product)
                .generateBrewTagPrefix(product.getAbbreviation(), PNC_PRODUCT_VERSION_2, systemConfig.getBrewTagPattern())
                .build();
        productVersion2 = productVersionRepository.save(productVersion2);

        final int DAYS_IN_A_WEEK = 7;
        final Date TODAY = Date
                .from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
        final Date ONE_WEEK_BEFORE_TODAY = Date.from(LocalDateTime.now().minusDays(DAYS_IN_A_WEEK)
                .atZone(ZoneId.systemDefault()).toInstant());
        final Date ONE_WEEK_AFTER_TODAY = Date.from(LocalDateTime.now().plusDays(DAYS_IN_A_WEEK)
                .atZone(ZoneId.systemDefault()).toInstant());

        demoProductMilestone1 = ProductMilestone.Builder.newBuilder()
                .version(PNC_PRODUCT_MILESTONE1)
                .startingDate(ONE_WEEK_BEFORE_TODAY)
                .plannedEndDate(TODAY)
                .productVersion(productVersion1).build();
        demoProductMilestone1 = productMilestoneRepository.save(demoProductMilestone1);

        ProductMilestone demoProductMilestone2 = ProductMilestone.Builder.newBuilder()
                .version(PNC_PRODUCT_MILESTONE2)
                .startingDate(TODAY)
                .plannedEndDate(ONE_WEEK_AFTER_TODAY)
                .productVersion(productVersion1).build();
        demoProductMilestone2 = productMilestoneRepository.save(demoProductMilestone2);

        ProductRelease productRelease = ProductRelease.Builder.newBuilder()
                .version(PNC_PRODUCT_RELEASE)
                .productMilestone(demoProductMilestone1).supportLevel(SupportLevel.EARLYACCESS)
                .build();
        productRelease = productReleaseRepository.save(productRelease);

        productVersion1.setCurrentProductMilestone(demoProductMilestone1);
        productVersion1 = productVersionRepository.save(productVersion1);

        // Example projects
        Project project1 = Project.Builder.newBuilder()
                .name(PNC_PROJECT_1_NAME)
                .description("Example Project for Newcastle Demo")
                .projectUrl("https://github.com/project-ncl/pnc")
                .build();
        Project project2 = Project.Builder.newBuilder()
                .name("Causeway")
                .description("Causeway - Koji integration")
                .projectUrl("https://github.com/project-ncl/causeway")
                .build();
        Project project3 = Project.Builder.newBuilder()
                .name("Pnc Build Agent")
                .description("Pnc Build Agent - remote client to execute commands.")
                .projectUrl("https://github.com/project-ncl/pnc-build-agent")
                .build();
        Project project4 = Project.Builder.newBuilder()
                .name("Dependency Analysis")
                .description("Dependency Analysis - Analise project dependencies.")
                .projectUrl("https://github.com/project-ncl/dependency-analysis")
                .build();
        Project project5 = Project.Builder.newBuilder()
                .name("termd")
                .description("Remote shell.")
                .projectUrl("https://github.com/project-ncl/termd")
                .build();

        projectRepository.save(project1);
        projectRepository.save(project2);
        projectRepository.save(project3);
        projectRepository.save(project4);
        projectRepository.save(project5);

        RepositoryConfiguration repositoryConfiguration1 = createRepositoryConfiguration(demoDataConfig.getInternalRepo(0));
        RepositoryConfiguration repositoryConfiguration2 = createRepositoryConfiguration(demoDataConfig.getInternalRepo(1));
        RepositoryConfiguration repositoryConfiguration3 = createRepositoryConfiguration(demoDataConfig.getInternalRepo(2));
        RepositoryConfiguration repositoryConfiguration4 = createRepositoryConfiguration(demoDataConfig.getInternalRepo(3));
        RepositoryConfiguration repositoryConfiguration5 = createRepositoryConfiguration(demoDataConfig.getInternalRepo(4));

        repositoryConfigurationRepository.save(repositoryConfiguration1);
        repositoryConfigurationRepository.save(repositoryConfiguration2);
        repositoryConfigurationRepository.save(repositoryConfiguration3);
        repositoryConfigurationRepository.save(repositoryConfiguration4);
        repositoryConfigurationRepository.save(repositoryConfiguration5);

        // Example build configurations
        Map<String, String> genericParameters = new HashMap<>();
        genericParameters.put("KEY", "VALUE");
        buildConfiguration1 = BuildConfiguration.Builder.newBuilder()
                .name(PNC_PROJECT_BUILD_CFG_ID)
                .project(project1)
                .description("Test build config for project newcastle")
                .buildType(BuildType.MVN)
                .buildEnvironment(environment1)
                .buildScript("mvn deploy -DskipTests=true")
                .repositoryConfiguration(repositoryConfiguration1)
                .productVersion(productVersion1)
                .scmRevision("*/v0.2")
                .genericParameters(genericParameters)
                .build();
        buildConfiguration1 = buildConfigurationRepository.save(buildConfiguration1);

        buildConfiguration2 = BuildConfiguration.Builder.newBuilder()
                .name("termd")
                .project(project2)
                .buildType(BuildType.MVN)
                .description("Test configueration for Termd.")
                .buildEnvironment(environment1)
                .buildScript("mvn deploy -DskipTests=true")
                .productVersion(productVersion1)
                .repositoryConfiguration(repositoryConfiguration2)
                .scmRevision("master")
                .build();
        buildConfiguration2 = buildConfigurationRepository.save(buildConfiguration2);

        BuildConfiguration buildConfiguration3 = BuildConfiguration.Builder.newBuilder()
                .name("pnc-build-agent-0.4")
                .project(project3)
                .description("Test config for Pnc Build Agent.")
                .buildType(BuildType.MVN)
                .buildEnvironment(environment1)
                .buildScript("mvn deploy -DskipTests=true")
                .productVersion(productVersion2)
                .repositoryConfiguration(repositoryConfiguration3)
                .dependency(buildConfiguration2)
                .build();
        buildConfiguration3 = buildConfigurationRepository.save(buildConfiguration3);

        BuildConfiguration buildConfiguration4 = BuildConfiguration.Builder.newBuilder()
                .name("dependency-analysis-1.3")
                .project(project4)
                .description("Test config for Dependency Analysis.")
                .buildType(BuildType.MVN)
                .buildEnvironment(environment1)
                .buildScript("mvn deploy -DskipTests=true")
                .repositoryConfiguration(repositoryConfiguration4)
                .dependency(buildConfiguration1)
                .build();
        buildConfiguration4 = buildConfigurationRepository.save(buildConfiguration4);

        BuildConfiguration buildConfiguration5 = BuildConfiguration.Builder.newBuilder()
                .name("maven-plugin-test")
                .project(project5)
                .description("Test build for Plugins with external downloads")
                .buildType(BuildType.MVN)
                .buildEnvironment(environment1)
                .buildScript("mvn clean deploy")
                .repositoryConfiguration(repositoryConfiguration5)
                .build();
        buildConfiguration5 = buildConfigurationRepository.save(buildConfiguration5);

        // Build config set containing the three example build configs
        buildConfigurationSet1 = BuildConfigurationSet.Builder.newBuilder()
                .name("Example Build Group 1")
                .buildConfiguration(buildConfiguration1).buildConfiguration(buildConfiguration2)
                .buildConfiguration(buildConfiguration3).productVersion(productVersion1).build();

        BuildConfigurationSet buildConfigurationSet2 = BuildConfigurationSet.Builder.newBuilder()
                .name("Fabric Build Group").buildConfiguration(buildConfiguration4).
                productVersion(productVersion1).build();

        demoUser = User.Builder.newBuilder().username("demo-user").firstName("Demo First Name")
                .lastName("Demo Last Name")
                .email("demo-user@pnc.com").build();

        pncAdminUser = User.Builder.newBuilder().username("pnc-admin").firstName("pnc-admin")
                .lastName("pnc-admin")
                .email("pnc-admin@pnc.com").build();

        buildConfigurationSetRepository.save(buildConfigurationSet1);
        buildConfigurationSetRepository.save(buildConfigurationSet2);
        demoUser = userRepository.save(demoUser);
        pncAdminUser = userRepository.save(pncAdminUser);

    }

    /**
     * Build record needs to be initialized in a separate transaction so that the audited build configuration can be set.
     */
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void initiliazeBuildRecordDemoData() {

        TargetRepository targetRepository = TargetRepository.newBuilder()
                .repositoryType(RepositoryType.MAVEN)
                .repositoryPath("builds-untested")
                .identifier("indy-maven")
                .temporaryRepo(false)
                .build();

        targetRepositoryRepository.save(targetRepository);

        Artifact builtArtifact1 = Artifact.Builder.newBuilder()
                .identifier("demo:built-artifact1:jar:1.0")
                .targetRepository(targetRepository)
                .filename("demo built artifact 1")
                .md5("4af310bf0ef67bc7d143f35818ea1ed2")
                .sha1("3a8ff25c890f2a4a283876a91037ff6c57474a14")
                .sha256("1660168483cb8a05d1cc2e77c861682a42ed9517ba945159d5538950c5db00fa")
                .size(10L)
                .build();
        Artifact builtArtifact2 = Artifact.Builder.newBuilder()
                .identifier("demo:built-artifact2:jar:1.0")
                .targetRepository(targetRepository)
                .filename("demo built artifact 2")
                .md5("4af310bf0ef67bc7d143f35818ea1ed2")
                .sha1("61dad16e14438d2d8c8cbd18b267d62944f37898")
                .sha256("2fafc2ed0f752ac2540283d48c5cd663254a853c5cb13dec02dce023fc7471a9")
                .size(11L)
                .build();

        builtArtifact1 = artifactRepository.save(builtArtifact1);
        builtArtifact2 = artifactRepository.save(builtArtifact2);

        Artifact importedArtifact1 = Artifact.Builder.newBuilder()
                .identifier("demo:imported-artifact1:jar:1.0")
                .targetRepository(targetRepository)
                .filename("demo imported artifact 1")
                .originUrl("http://central/import1.jar")
                .importDate(Date.from(Instant.now()))
                .md5("md-fake-abcd1234")
                .sha1("sha1-fake-abcd1234")
                .sha256("sha256-fake-abcd1234")
                .size(10L)
                .deployPath("http://google.pl/imported1").build();
        Artifact importedArtifact2 = Artifact.Builder.newBuilder()
                .identifier("demo:imported-artifact2:jar:1.0")
                .targetRepository(targetRepository)
                .filename("demo imported artifact 2")
                .originUrl("http://central/import2.jar")
                .importDate(Date.from(Instant.now()))
                .md5("md-fake-abcd1234")
                .sha1("sha1-fake-abcd1234")
                .sha256("sha256-fake-abcd1234")
                .size(10L)
                .deployPath("http://google.pl/imported2").build();

        importedArtifact1 = artifactRepository.save(importedArtifact1);
        importedArtifact2 = artifactRepository.save(importedArtifact2);

        Set<BuildRecord> buildRecords = new HashSet<>();

        final int INITIAL_REVISION = 1;
        IdRev buildConfig1AuditIdRev = new IdRev(buildConfiguration1.getId(), INITIAL_REVISION);
        BuildConfigurationAudited buildConfigAudited1 = buildConfigurationAuditedRepository
                .queryById(buildConfig1AuditIdRev);
        if (buildConfigAudited1 != null) {

            int nextId = datastore.getNextBuildRecordId();
            log.info("####nextId: " + nextId);

            BuildRecord buildRecord1 = BuildRecord.Builder.newBuilder().id(nextId)
                    .buildConfigurationAudited(buildConfigAudited1)
                    .submitTime(Timestamp.from(Instant.now().minus(8, ChronoUnit.MINUTES)))
                    .startTime(Timestamp.from(Instant.now().minus(5, ChronoUnit.MINUTES)))
                    .endTime(Timestamp.from(Instant.now()))
                    .builtArtifact(builtArtifact1)
                    .builtArtifact(builtArtifact2)
                    .dependency(importedArtifact1)
                    .dependency(importedArtifact2)
                    .user(demoUser)
                    .buildLog("Very short demo log: The quick brown fox jumps over the lazy dog.")
                    .status(BuildStatus.SUCCESS)
                    .productMilestone(demoProductMilestone1)
                    .sshCommand("ssh worker@localhost -P 9999")
                    .sshPassword("dontchangeme")
                    .buildEnvironment(buildConfigAudited1.getBuildEnvironment())
                    .executionRootName("org.jboss.pnc:parent")
                    .executionRootVersion("1.2.3")
                    .temporaryBuild(false)
                    .build();

            buildRecordRepository.save(buildRecord1);
            buildRecords.add(buildRecord1);

        }

        Artifact builtArtifact3 = Artifact.Builder.newBuilder()
                .identifier("demo:built-artifact3:jar:1.0")
                .targetRepository(targetRepository)
                .filename("demo built artifact 3")
                .md5("17353a18678c6c249e3052edec2e4c5c")
                .sha1("61dad16e14438d2d8c8cbd18b267d62944f37898")
                .sha256("1660168483cb8a05d1cc2e77c861682a42ed9517ba945159d5538950c5db00fa")
                .size(10L)
                .deployPath("http://google.pl/built3")
                .build();
        Artifact builtArtifact4 = Artifact.Builder.newBuilder()
                .identifier("demo:built-artifact4:jar:1.0")
                .targetRepository(targetRepository)
                .filename("demo built artifact 4")
                .md5("md-fake-abcd1234")
                .sha1("sha1-fake-abcd1234")
                .sha256("sha256-fake-abcd1234")
                .size(10L)
                .deployPath("http://google.pl/built4")
                .build();

        builtArtifact3 = artifactRepository.save(builtArtifact3);
        builtArtifact4 = artifactRepository.save(builtArtifact4);

        Artifact dependencyBuiltArtifact1 = artifactRepository
                .queryByPredicates(withIdentifierAndSha256(builtArtifact1.getIdentifier(),
                        builtArtifact1.getSha256()));

        IdRev buildConfig2AuditIdRev = new IdRev(buildConfiguration2.getId(), INITIAL_REVISION);
        BuildConfigurationAudited buildConfigAudited2 = buildConfigurationAuditedRepository
                .queryById(buildConfig2AuditIdRev);
        if (buildConfigAudited2 != null) {

            int nextId = datastore.getNextBuildRecordId();
            log.info("####nextId: " + nextId);

            BuildRecord buildRecord2 = BuildRecord.Builder.newBuilder().id(nextId)
                    .buildConfigurationAudited(buildConfigAudited2)
                    .submitTime(Timestamp.from(Instant.now().minus(8, ChronoUnit.MINUTES)))
                    .startTime(Timestamp.from(Instant.now().minus(5, ChronoUnit.MINUTES)))
                    .endTime(Timestamp.from(Instant.now()))
                    .builtArtifact(builtArtifact3)
                    .builtArtifact(builtArtifact4)
                    .dependency(dependencyBuiltArtifact1)
                    .dependency(importedArtifact1)
                    .user(demoUser)
                    .buildLog("Very short demo log: The quick brown fox jumps over the lazy dog.")
                    .status(BuildStatus.SUCCESS)
                    .buildEnvironment(buildConfigAudited2.getBuildEnvironment())
                    .executionRootName("org.jboss.pnc:parent")
                    .executionRootVersion("1.2.4")
                    .temporaryBuild(false)
                    .build();

            buildRecordRepository.save(buildRecord2);
            buildRecords.add(buildRecord2);
        }

        BuildConfigSetRecord buildConfigSetRecord1 = BuildConfigSetRecord.Builder.newBuilder()
                .buildConfigurationSet(buildConfigurationSet1)
                .startTime(Timestamp.from(Instant.now()))
                .endTime(Timestamp.from(Instant.now()))
                .user(demoUser)
                .status(BuildStatus.FAILED)
                .temporaryBuild(false)
                .build();
        buildConfigSetRecordRepository.save(buildConfigSetRecord1);

        BuildConfigSetRecord buildConfigSetRecord2 = BuildConfigSetRecord.Builder.newBuilder()
                .buildConfigurationSet(buildConfigurationSet1)
                .buildRecords(buildRecords)
                .startTime(Timestamp.from(Instant.now()))
                .endTime(Timestamp.from(Instant.now()))
                .user(demoUser)
                .status(BuildStatus.SUCCESS)
                .temporaryBuild(false)
                .build();
        buildConfigSetRecordRepository.save(buildConfigSetRecord2);

        demoProductMilestone1 = productMilestoneRepository.queryById(demoProductMilestone1.getId());
        demoProductMilestone1.addDistributedArtifact(builtArtifact1);
        demoProductMilestone1.addDistributedArtifact(builtArtifact3);
        demoProductMilestone1.addDistributedArtifact(importedArtifact2);
        demoProductMilestone1 = productMilestoneRepository.save(demoProductMilestone1);

    }

    private RepositoryConfiguration createRepositoryConfiguration(String internalScmUrl) {
        return  RepositoryConfiguration.Builder.newBuilder()
                .internalUrl(internalScmUrl)
                .build();
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.spi.coordinator;

import lombok.Getter;

import org.jboss.pnc.model.BuildConfigSetRecord;
import org.jboss.pnc.model.BuildConfigurationAudited;
import org.jboss.pnc.enums.BuildCoordinationStatus;
import org.jboss.pnc.enums.BuildStatus;
import org.jboss.pnc.spi.BuildOptions;
import org.jboss.pnc.spi.BuildSetStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Created by Matej Lazar on 2015-03-26.
 */
public class BuildSetTask {

    private final Logger log = LoggerFactory.getLogger(BuildCoordinator.class);

    private final Optional<BuildConfigSetRecord> buildConfigSetRecord;

    @Getter
    private final BuildOptions buildOptions;

    private BuildSetStatus status;

    private String statusDescription;

    private Date startTime;

    private final Set<BuildTask> buildTasks = new HashSet<>();

    /**
     * Create build set task for running a single build or set of builds
     *
     * @param buildConfigSetRecord The config set record which will be stored to the db
     * @param buildOptions Build parameters
     */
    private BuildSetTask(
            BuildConfigSetRecord buildConfigSetRecord, //TODO decouple datastore entity
            BuildOptions buildOptions) {
        this.buildConfigSetRecord = Optional.ofNullable(buildConfigSetRecord);
        this.buildOptions = buildOptions;
    }

    public void setStatus(BuildSetStatus status) {
        this.status = status;
    }

    /**
     * Notify the set that the state of one of it's tasks has changed.
     *
     */
    public void taskStatusUpdatedToFinalState() {
        // If any of the build tasks have failed or all are complete, then the build set is done
        if(buildTasks.stream().anyMatch(bt -> bt.getStatus().equals(BuildCoordinationStatus.CANCELLED))) {
            log.debug("Marking build set as CANCELLED as one or more tasks were cancelled. BuildSetTask: {}", this);
            if (log.isDebugEnabled()) {
                logTasksStatus(buildTasks);
            }
            buildConfigSetRecord.ifPresent(r -> r.setStatus(BuildStatus.CANCELLED));
            finishBuildSetTask();
        } else if(buildTasks.stream().anyMatch(bt -> bt.getStatus().hasFailed())) {
            log.debug("Marking build set as FAILED as one or more tasks failed. BuildSetTask: {}", this);
            if (log.isDebugEnabled()) {
                logTasksStatus(buildTasks);
            }
            buildConfigSetRecord.ifPresent(r -> r.setStatus(BuildStatus.FAILED));
            finishBuildSetTask();
        } else if (buildTasks.stream().allMatch(bt -> bt.getStatus().isCompleted())) {
            log.debug("Marking build set as SUCCESS. BuildSetTask: {}", this);
            buildConfigSetRecord.ifPresent(r -> r.setStatus(BuildStatus.SUCCESS));
            finishBuildSetTask();
        } else {
            if (log.isTraceEnabled()) {
                List<Integer> running = buildTasks.stream()
                        .filter(bt -> !bt.getStatus().isCompleted())
                        .filter(bt -> !bt.getStatus().hasFailed())
                        .map(BuildTask::getId)
                        .collect(Collectors.toList());
                log.trace("There are still running or waiting builds [{}].", running);
            }
        }
    }

    private void logTasksStatus(Set<BuildTask> buildTasks) {
        String taskStatuses = buildTasks.stream().map(bt -> "TaskId " + bt.getId() + ":" + bt.getStatus()).collect(Collectors.joining("; "));
        log.debug("Tasks statuses: {}", taskStatuses);
    }

    private void finishBuildSetTask() {
        buildConfigSetRecord.ifPresent(r -> r.setEndTime(new Date()));
    }

    public BuildSetStatus getStatus() {
        return status;
    }

    public void setStatusDescription(String statusDescription) {
        this.statusDescription = statusDescription;
    }

    public String getStatusDescription() {
        return statusDescription;
    }

    public Date getStartTime() {
        return startTime;
    }

    public Set<BuildTask> getBuildTasks() {
        return buildTasks;
    }

    public void addBuildTask(BuildTask buildTask) {
        buildTasks.add(buildTask);
    }

    /**
     * Get the build task which contains the given audited build configuration
     *
     * @param buildConfigurationAudited A BuildConfigurationAudited entity
     * @return The build task with the matching configuration, or null if there is none
     */
    public BuildTask getBuildTask(BuildConfigurationAudited buildConfigurationAudited) {
        return buildTasks.stream().filter((bt) -> bt.getBuildConfigurationAudited().equals(buildConfigurationAudited)).findFirst().orElse(null);
    }

    public Integer getId() {
        return buildConfigSetRecord.map(BuildConfigSetRecord::getId).orElse(null);
    }

    public Optional<BuildConfigSetRecord> getBuildConfigSetRecord() {
        return buildConfigSetRecord;
    }

    public static class Builder {
        private BuildConfigSetRecord buildConfigSetRecord; //TODO decouple datastore entity
        private BuildOptions buildOptions;
        private Date startTime;

        private Builder() {}

        public static Builder newBuilder() {
            return new Builder();
        }

        public Builder buildConfigSetRecord(BuildConfigSetRecord buildConfigSetRecord) {
            this.buildConfigSetRecord = buildConfigSetRecord;
            this.startTime(buildConfigSetRecord.getStartTime());
            return this;
        }
        public Builder buildOptions(BuildOptions buildOptions) {
            this.buildOptions = buildOptions;
            return this;
        }

        public Builder startTime(Date startTime) {
            this.startTime = startTime;
            return this;
        }

        public BuildSetTask build() {
            BuildSetTask buildSetTask = new BuildSetTask(buildConfigSetRecord, buildOptions);
            buildSetTask.startTime = this.startTime;
            return buildSetTask;
        }
    }

    public boolean isFinished() {
        return this
                .getBuildTasks()
                .stream()
                .allMatch(t -> t.getStatus().isCompleted());
    }

    @Override
    public String toString() {
        return "BuildSetTask{" +
                "status=" + status +
                ", statusDescription='" + statusDescription + '\'' +
                ", submitTime=" + getStartTime() +
                ", buildTasks=" + buildTasks +
                '}';
    }
}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.facade.providers;

import org.jboss.pnc.dto.SCMRepository;
import org.jboss.pnc.dto.response.Page;
import org.jboss.pnc.facade.mapper.api.SCMRepositoryMapper;
import org.jboss.pnc.facade.providers.api.SCMRepositoryProvider;
import org.jboss.pnc.model.RepositoryConfiguration;
import org.jboss.pnc.spi.datastore.repositories.RepositoryConfigurationRepository;
import org.jboss.pnc.spi.datastore.repositories.api.Predicate;

import javax.ejb.Stateless;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

import static org.jboss.pnc.spi.datastore.predicates.RepositoryConfigurationPredicates.matchByScmUrl;
import static org.jboss.pnc.spi.datastore.predicates.RepositoryConfigurationPredicates.searchByScmUrl;

@Stateless
public class SCMRepositoryProviderImpl
        extends AbstractProvider<RepositoryConfiguration, SCMRepository, SCMRepository> implements SCMRepositoryProvider {

    @Inject
    public SCMRepositoryProviderImpl(RepositoryConfigurationRepository repository,
                                     SCMRepositoryMapper mapper) {
        super(repository, mapper, RepositoryConfiguration.class);
    }

    public Page<SCMRepository> getAllWithMatchAndSearchUrl(int pageIndex,
                                                           int pageSize,
                                                           String sortingRsql,
                                                           String query,
                                                           String matchUrl,
                                                           String searchUrl) {

        List<Predicate<RepositoryConfiguration>> predicates = new ArrayList<>();

        addToListIfStringNotNullAndNotEmpty(predicates, matchUrl, matchByScmUrl(matchUrl));
        addToListIfStringNotNullAndNotEmpty(predicates, searchUrl, searchByScmUrl(searchUrl));

        // transform list to array for 'predicates' varargs in 'queryForCollection' method
        Predicate<RepositoryConfiguration>[] predicatesArray = new Predicate[predicates.size()];
        predicatesArray = predicates.toArray(predicatesArray);

        return queryForCollection(pageIndex, pageSize, sortingRsql, query, predicatesArray);
    }

    /**
     * Add item to the list if the string is not null *and* not empty
     *
     * @param list
     * @param str
     * @param item
     * @param <T>
     */
    private <T> void addToListIfStringNotNullAndNotEmpty(List<T> list, String str, T item) {

        if (str != null && !str.isEmpty()) {
            list.add(item);
        }
    }
}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.indyrepositorymanager;

import org.commonjava.indy.client.core.Indy;
import org.commonjava.indy.client.core.IndyClientException;
import org.commonjava.indy.model.core.Group;
import org.commonjava.indy.model.core.StoreKey;
import org.commonjava.indy.model.core.StoreType;
import org.jboss.pnc.spi.repositorymanager.RepositoryManagerException;
import org.jboss.pnc.spi.repositorymanager.model.CompletedRepositoryPromotion;
import org.jboss.pnc.spi.repositorymanager.model.RunningRepositoryPromotion;

import java.util.function.Consumer;

public class IndyRunningPromotion implements RunningRepositoryPromotion {

    private String pakageType;
    private StoreType fromType;
    private String fromId;
    private String toId;
    private Indy indy;

    public IndyRunningPromotion(String pakageType, StoreType fromType, String fromId, String toId, Indy indy) {
        this.pakageType = pakageType;
        this.fromType = fromType;
        this.fromId = fromId;
        this.toId = toId;
        this.indy = indy;
    }

    /**
     * Trigger the repository promotion configured for this instance, and send the result to the appropriate consumer.
     *
     * @param onComplete The consumer which will handle non-error results
     * @param onError Handles errors
     */
    @Override
    public void monitor(Consumer<CompletedRepositoryPromotion> onComplete, Consumer<Exception> onError) {
        try {
            StoreKey fromKey = new StoreKey(pakageType, fromType, fromId);
            if (!indy.stores().exists(fromKey)) {
                throw new RepositoryManagerException("No such %s repository: %s", fromType.singularEndpointName(), fromId);
            }

            StoreKey toKey = new StoreKey(pakageType, StoreType.group, toId);
            Group recordSetGroup = indy.stores().load(toKey, Group.class);
            if (recordSetGroup == null) {
                throw new RepositoryManagerException("No such group: %s", toId);
            }

            recordSetGroup.addConstituent(fromKey);

            boolean result = indy.stores().update(recordSetGroup,
                    "Promoting " + fromType.singularEndpointName() + " repository : " + fromId + " to group: " + toId);

            onComplete.accept(new IndyCompletedPromotion(result));

        } catch (IndyClientException | RepositoryManagerException e) {
            onError.accept(e);
        }
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.common.graph;

import org.jboss.util.graph.Edge;
import org.jboss.util.graph.Graph;
import org.jboss.util.graph.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;

/**
 * @author Matej Lazar
 */
public class GraphUtils {

    private static Logger logger = LoggerFactory.getLogger(GraphUtils.class);

    /**
     * Adds all elements from toMerge to target.
     */
    public static <T> void merge(Graph<T> target, Graph<T> toMerge) {
        for (Vertex<T> vertex : toMerge.getVerticies()) {
            target.addVertex(vertex);
        }

        // merge edges
        List<Edge<T>> edges = target.getEdges();
        for (Edge newEdge : toMerge.getEdges()) {
            Optional<Edge<T>> any = edges.stream()
                    .filter(existing ->
                            existing.getFrom().getName().equals(newEdge.getFrom().getName())
                             && existing.getTo().getName().equals(newEdge.getTo().getName())
                    ).findAny();
            if (!any.isPresent()) {
                edges.add(newEdge);
            }
        }
    }
}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ReadEnvProperty {

    private static final Logger log = LoggerFactory.getLogger(ReadEnvProperty.class);
    /**
     * If propertyName has no value (either specified in system property or environment property), then just return
     * the default value. System property value has priority over environment property value.
     *
     * If value can't be parsed, just return the default value.
     *
     * @param propertyName property name to check the value
     * @param defaultValue default value to use
     *
     * @return value from property, or default value
     */
    public int getIntValueFromPropertyOrDefault(String propertyName, int defaultValue) {

        int value = defaultValue;

        String valueEnv = System.getenv(propertyName);
        String valueSys = System.getProperty(propertyName);

        try {
            if (valueSys != null) {
                value = Integer.parseInt(valueSys);
            } else if (valueEnv != null) {
                value = Integer.parseInt(valueEnv);
            }
            return value;
        } catch (NumberFormatException e) {
            log.warn("Could not parse the '" + propertyName +
                    "' system property. Using default value: " + defaultValue);
            return value;
        }
    }
}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Scanner;

/**
 * Created by Matej Lazar on 2014-12-06.
 */
public class IoUtils {

    private static final Logger logger = LoggerFactory.getLogger(IoUtils.class);

    /**
     * @param name Resource name to use.
     * @param classLoader Classloader to use
     * @return The configuration string
     * @throws IOException Thrown if data couldn't be loaded
     */
    public static String readResource(String name, ClassLoader classLoader) throws IOException {
        String configString = "";
        InputStream is = classLoader.getResourceAsStream(name);
        if (is == null) {
            throw new RuntimeException("Cannot read resource: " + name);
        }
        try {
            configString = new Scanner(is, Charset.defaultCharset().name()).useDelimiter("\\A").next();
        } finally {
            is.close();
        }
        return configString;
    }

    /**
     * Reads whole content of file to String
     *
     * @param fileName Existing file
     * @return File content
     * @throws IOException Thrown if data couldn't be loaded
     */
    public static String readFileAsString(File fileName) throws IOException {
        try (Scanner sc = new Scanner(fileName, Charset.defaultCharset().name())) {
            sc.useDelimiter("\\A");
            return sc.next();
        }
    }

    /**
     * Reads whole content of input stream to String
     *
     * @param stream Opened stream
     * @return Stream content
     * @throws IOException Thrown if data couldn't be loaded
     */
    public static String readStreamAsString(InputStream stream) throws IOException {
        try (Scanner sc = new Scanner(stream, Charset.defaultCharset().name())) {
            sc.useDelimiter("\\A");
            return sc.next();
        }
    }

    /**
     *
readFileOrResource.

     *
     * @param systemPropertyName Name of system property to override default file name
     * @param defaultFileName Filename to use if system property doesn't exist.
     * @param classLoader Classloader to use
     * @return The configuration string
     * @throws IOException Thrown if data couldn't be loaded
     */
    public static String readFileOrResource(String systemPropertyName, String defaultFileName,
            ClassLoader classLoader) throws IOException {

        String templateFileName = System.getProperty(systemPropertyName);

        if (templateFileName == null) {
            templateFileName = defaultFileName;
        }

        File file = new File(templateFileName); // try full path

        String configString;
        if (file.exists()) {
            try {
                byte[] encoded;
                encoded = Files.readAllBytes(Paths.get(file.getPath()));
                configString = new String(encoded, Charset.defaultCharset());
            } catch (IOException e) {
                throw new IOException("Cannot load " + templateFileName + ".", e);
            }
        } else {
            try {
                configString = IoUtils.readResource(templateFileName, classLoader);
            } catch (IOException e) {
                throw new IOException("Cannot load " + templateFileName + ".", e);
            }
        }

        if (configString == null) {
            throw new IOException("Cannot load " + templateFileName + ".");
        }

        return configString;
    }
}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.spi.datastore.predicates;

import org.jboss.pnc.common.util.StringUtils;
import org.jboss.pnc.common.util.UrlUtils;
import org.jboss.pnc.model.RepositoryConfiguration;
import org.jboss.pnc.model.RepositoryConfiguration_;
import org.jboss.pnc.spi.datastore.repositories.api.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RepositoryConfigurationPredicates {

    private static Logger logger = LoggerFactory.getLogger(RepositoryConfigurationPredicates.class);

    public static Predicate<RepositoryConfiguration> withExactInternalScmRepoUrl(String internalUrl) {
        return (root, query, cb) -> cb.equal(root.get(RepositoryConfiguration_.internalUrl), internalUrl);
    }

    /**
     * Queries against normalized version with stripped protocol and .git extension
     */
    public static Predicate<RepositoryConfiguration> withInternalScmRepoUrl(String internalUrl) {
        String urlStripped = StringUtils.stripSuffix(UrlUtils.keepHostAndPathOnly(internalUrl), ".git");
        return (root, query, cb) -> cb.equal(root.get(RepositoryConfiguration_.internalUrlNormalized), urlStripped);
    }

    /**
     * Queries against normalized version with stripped protocol and .git extension
     */
    public static Predicate<RepositoryConfiguration> withExternalScmRepoUrl(String externalScmRepoUrl) {
        String urlStripped = StringUtils.stripSuffix(UrlUtils.keepHostAndPathOnly(externalScmRepoUrl), ".git");
        return (root, query, cb) -> cb.equal(root.get(RepositoryConfiguration_.externalUrlNormalized), urlStripped);
    }

    public static Predicate<RepositoryConfiguration> searchByScmUrl(String scmUrl) {
        String urlStripped = UrlUtils.keepHostAndPathOnly(scmUrl);
        urlStripped = StringUtils.stripSuffix(urlStripped, ".git");

        String pattern = "%" + urlStripped + "%";
        logger.trace("Searching for pattern: {}.", pattern);

        return (root, query, cb) -> cb.or(
                cb.like(root.get(RepositoryConfiguration_.internalUrlNormalized), pattern),
                cb.like(root.get(RepositoryConfiguration_.externalUrlNormalized), pattern));
    }

    public static Predicate<RepositoryConfiguration> matchByScmUrl(String scmUrl) {
        final String urlStripped = StringUtils.stripSuffix(UrlUtils.keepHostAndPathOnly(scmUrl), ".git");

        logger.trace("Searching for pattern: {}.", urlStripped);

        return (root, query, cb) -> cb.or(
                cb.equal(root.get(RepositoryConfiguration_.internalUrlNormalized), urlStripped),
                cb.equal(root.get(RepositoryConfiguration_.externalUrlNormalized), urlStripped));
    }
}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.rest.endpoint;

import io.swagger.v3.oas.annotations.Hidden;
import org.jboss.pnc.coordinator.builder.BuildQueue;
import org.jboss.pnc.coordinator.notifications.buildTask.MessageSenderProvider;
import org.jboss.pnc.dto.Build;
import org.jboss.pnc.enums.BuildCoordinationStatus;
import org.jboss.pnc.messaging.spi.MessageSender;
import org.jboss.pnc.spi.coordinator.events.DefaultBuildStatusChangedEvent;
import org.jboss.pnc.spi.events.BuildCoordinationStatusChangedEvent;

import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Optional;

/**
 * Author: Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com
 * Date: 1/25/17
 * Time: 2:25 PM
 */
@Hidden
@Path("/debug")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class DebugEndpoint {

    @Inject
    private BuildQueue buildQueue;

    @Inject
    private MessageSenderProvider messageSenderProvider;

    @Inject
    private Event<BuildCoordinationStatusChangedEvent> buildStatusChangedEventNotifier;

    @GET
    @Path("/build-queue")
    public Response getBuildQueueInfo() {
        String info = buildQueue.getDebugInfo();
        return Response.ok(info).build();
    }

    /**
     *  curl -v -X POST http://localhost:8080/pnc-rest/rest/debug/mq-send-dummy-message
     *  curl -v -X POST http://localhost:8080/pnc-rest/rest/debug/mq-send-dummy-message?type=status
     */
    @POST
    @Path("/mq-send-dummy-message")
    public Response sendDummyMessageToQueue(@QueryParam("type") String type) {
        Optional<MessageSender> messageSender = messageSenderProvider.getMessageSender();
        if (!messageSender.isPresent()) {
            return Response
                    .status(Response.Status.SERVICE_UNAVAILABLE)
                    .entity("Message sender is not available to inject.")
                    .build();
        } else {
            if (type != null && type.equals("status")) {
                buildStatusChangedEventNotifier.fire(new DefaultBuildStatusChangedEvent(
                        newBuild(),BuildCoordinationStatus.CANCELLED
                        ));
            } else {
                messageSender.get().sendToTopic("Test Message.");
            }
            return Response.ok().build();
        }
    }

    public static Build newBuild() {
        return Build.builder()
                .id(1)
                .status(BuildCoordinationStatus.ENQUEUED)
                .buildContentId("build-42")
                .temporaryBuild(true)
                .build();
    }

}


/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.pnc.managers;

import org.jboss.pnc.bpm.BpmEventType;
import org.jboss.pnc.bpm.BpmManager;
import org.jboss.pnc.bpm.BpmTask;
import org.jboss.pnc.bpm.task.MilestoneReleaseTask;
import org.jboss.pnc.model.Artifact;
import org.jboss.pnc.model.BuildConfigurationAudited;
import org.jboss.pnc.model.BuildRecord;
import org.jboss.pnc.model.BuildRecordPushResult;
import org.jboss.pnc.enums.MilestoneReleaseStatus;
import org.jboss.pnc.model.ProductMilestone;
import org.jboss.pnc.model.ProductMilestoneRelease;
import org.jboss.pnc.model.ProductVersion;
import org.jboss.pnc.rest.restmodel.bpm.BpmNotificationRest;
import org.jboss.pnc.rest.restmodel.bpm.BpmStringMapNotificationRest;
import org.jboss.pnc.rest.restmodel.causeway.ArtifactImportError;
import org.jboss.pnc.rest.restmodel.causeway.BuildImportResultRest;
import org.jboss.pnc.rest.restmodel.causeway.BuildImportStatus;
import org.jboss.pnc.rest.restmodel.causeway.MilestoneReleaseResultRest;
import org.jboss.pnc.spi.datastore.repositories.ArtifactRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildRecordPushResultRepository;
import org.jboss.pnc.spi.datastore.repositories.BuildRecordRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductMilestoneReleaseRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductMilestoneRepository;
import org.jboss.pnc.spi.datastore.repositories.ProductVersionRepository;
import org.jboss.pnc.spi.exception.CoreException;
import org.jboss.pnc.spi.exception.ProcessManagerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ejb.Stateless;
import javax.inject.Inject;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;

import org.jboss.pnc.enums.BuildPushStatus;
import static org.jboss.pnc.common.util.CollectionUtils.ofNullableCollection;

/**
 * Author: Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com
 * Date: 8/31/16
 * Time: 8:42 AM
 */
@Stateless
public class ProductMilestoneReleaseManager {

    private static final Logger log = LoggerFactory.getLogger(ProductMilestoneReleaseManager.class);
    public static final String BREW_ID = "brewId";
    public static final String BREW_LINK = "brewLink";

    private BpmManager bpmManager;

    private ArtifactRepository artifactRepository;
    private ProductVersionRepository productVersionRepository;
    private BuildRecordRepository buildRecordRepository;
    private ProductMilestoneReleaseRepository productMilestoneReleaseRepository;
    private ProductMilestoneRepository milestoneRepository;
    private BuildRecordPushResultRepository buildRecordPushResultRepository;

    @Deprecated // for ejb
    public ProductMilestoneReleaseManager() {
    }

    @Inject
    public ProductMilestoneReleaseManager(
            ProductMilestoneReleaseRepository productMilestoneReleaseRepository,
            BpmManager bpmManager,
            ArtifactRepository artifactRepository,
            ProductVersionRepository productVersionRepository,
            BuildRecordRepository buildRecordRepository,
            ProductMilestoneRepository milestoneRepository,
            BuildRecordPushResultRepository buildRecordPushResultRepository) {
        this.productMilestoneReleaseRepository = productMilestoneReleaseRepository;
        this.bpmManager = bpmManager;
        this.artifactRepository = artifactRepository;
        this.productVersionRepository = productVersionRepository;
        this.buildRecordRepository = buildRecordRepository;
        this.milestoneRepository = milestoneRepository;
        this.buildRecordPushResultRepository = buildRecordPushResultRepository;
    }

    /**
     * Starts milestone release process
     *
     * @param milestone product milestone to start the release for
     * @param accessToken
     */
    public void startRelease(ProductMilestone milestone, String accessToken) {
        ProductMilestoneRelease release = triggerRelease(milestone, accessToken);
        productMilestoneReleaseRepository.save(release);
    }

    public void cancel(ProductMilestone milestoneInDb) {
        Collection<BpmTask> activeTasks = bpmManager.getActiveTasks();
        Optional<MilestoneReleaseTask> milestoneReleaseTask = activeTasks.stream()
                .map(task -> (MilestoneReleaseTask) task)
                .filter(task -> task.getMilestone().getId().equals(milestoneInDb.getId()))
                .findAny();

        if (milestoneReleaseTask.isPresent()) {
            bpmManager.cancelTask(milestoneReleaseTask.get());
        }

        ProductMilestoneRelease milestoneRelease = productMilestoneReleaseRepository.findLatestByMilestone(milestoneInDb);
        milestoneRelease.setStatus(MilestoneReleaseStatus.CANCELED);
        productMilestoneReleaseRepository.save(milestoneRelease);
    }

    public boolean noReleaseInProgress(ProductMilestone milestone) {
        ProductMilestoneRelease latestRelease = productMilestoneReleaseRepository.findLatestByMilestone(milestone);

        return latestRelease == null || latestRelease.getStatus() != MilestoneReleaseStatus.IN_PROGRESS;
    }

    private <T extends BpmNotificationRest> ProductMilestoneRelease triggerRelease(ProductMilestone milestone, String accessToken) {
        ProductMilestoneRelease release = new ProductMilestoneRelease();
        release.setStartingDate(new Date());
        release.setMilestone(milestone);
        try {
            MilestoneReleaseTask releaseTask = new MilestoneReleaseTask(milestone, accessToken);
            Integer id = milestone.getId();
            releaseTask.<MilestoneReleaseResultRest>addListener(BpmEventType.BREW_IMPORT_SUCCESS, r -> onSuccessfulPush(id, r));
            releaseTask.<BpmStringMapNotificationRest>addListener(BpmEventType.BREW_IMPORT_ERROR, r -> onFailedPush(milestone.getId(), r));
            release.setStatus(MilestoneReleaseStatus.IN_PROGRESS);
            bpmManager.startTask(releaseTask);
            release.setLog("Brew push task started\n");

            return release;
        } catch (CoreException e) {
            log.error("Error trying to start brew push task for milestone: {}", milestone.getId(), e);
            release.setLog("Brew push BPM task creation failed.\nCheck log for more details.\n");
            release.setStatus(MilestoneReleaseStatus.SYSTEM_ERROR);
            release.setEndDate(new Date());
            return release;
        }
    }

    private void onSuccessfulPush(Integer milestoneId, MilestoneReleaseResultRest result) {
        log.debug("Storing milestone release result: {}", result);
        withMilestone(milestoneId, result, this::storeSuccess);
    }

    private void onFailedPush(Integer milestoneId, BpmStringMapNotificationRest result) {
        log.debug("Storing failed milestone release result: {}", result);
        withMilestone(milestoneId, result, this::storeFailure);
    }

    private <T> void withMilestone(Integer milestoneId, T result, BiConsumer<ProductMilestone, T> consumer) {
        ProductMilestone milestone = milestoneRepository.queryById(milestoneId);

        if (milestone == null) {
            log.error("No milestone found for milestone id {}", milestoneId);
            return;
        }
        consumer.accept(milestone, result);
    }

    private void storeSuccess(ProductMilestone milestone, MilestoneReleaseResultRest result) {
        String message = describeCompletedPush(result);
        updateRelease(milestone, message, result.getReleaseStatus().getMilestoneReleaseStatus());

        for (BuildImportResultRest buildRest : ofNullableCollection(result.getBuilds())) {
            storeBrewBuildParameters(buildRest);
        }

        if (result.getReleaseStatus().getMilestoneReleaseStatus() == MilestoneReleaseStatus.SUCCEEDED) {
            // set milestone end date to now when the release process is successful
            milestone.setEndDate(new Date());
            milestoneRepository.save(milestone);

            removeCurrentFlagFromMilestone(milestone);
        }
    }

    private <T> void storeFailure(ProductMilestone milestone, BpmStringMapNotificationRest result) {
        updateRelease(milestone, "BREW IMPORT FAILED\nResult: " + result, MilestoneReleaseStatus.SYSTEM_ERROR);
    }

    private void storeBrewBuildParameters(BuildImportResultRest buildRest) {
        Integer recordId = buildRest.getBuildRecordId();
        BuildRecord record = buildRecordRepository.queryById(recordId);
        if (record == null) {
            log.error("No record found for record id: {}, skipped saving info: {}", recordId, buildRest);
            return;
        }

        String combinedLog = ArtifactImportError.combineMessages(buildRest.getErrorMessage(), buildRest.getErrors());

        BuildPushStatus status;
        try {
            status = convertStatus(buildRest.getStatus());
        } catch (ProcessManagerException e) {
            log.error("Cannot convert status.", e);
            throw new RuntimeException("Cannot convert status.", e);
        }

        BuildRecordPushResult buildRecordPush = BuildRecordPushResult.newBuilder()
                .buildRecord(record)
                .status(status)
                .log(combinedLog)
                .brewBuildId(buildRest.getBrewBuildId())
                .brewBuildUrl(buildRest.getBrewBuildUrl())
                .tagPrefix("") //TODO tag!
                .build();
        buildRecordPushResultRepository.save(buildRecordPush);
    }

    private BuildPushStatus convertStatus(BuildImportStatus status) throws ProcessManagerException {
        switch (status) {
            case SUCCESSFUL:
                return BuildPushStatus.SUCCESS;
            case FAILED:
                return BuildPushStatus.FAILED;
            case ERROR:
                return BuildPushStatus.SYSTEM_ERROR;
        }
        throw new ProcessManagerException("Invalid BuildImportStatus: " + status.toString());
    }

    private void updateRelease(ProductMilestone milestone, String message, MilestoneReleaseStatus status) {
        ProductMilestoneRelease release = productMilestoneReleaseRepository.findLatestByMilestone(milestone);
        if (release == null) {
            log.error("No milestone release found for milestone {}", milestone.getId());
            return;
        }
        if (status != MilestoneReleaseStatus.IN_PROGRESS) {
            release.setEndDate(new Date());
        }
        release.setStatus(status);
        release.setLog(release.getLog() + message);
        productMilestoneReleaseRepository.save(release);
    }

    private String describeCompletedPush(MilestoneReleaseResultRest result) {
        boolean success = result.isSuccessful();
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Brew push ").append(success ? "SUCCEEDED" : "FAILED").append("\n");
        stringBuilder.append("Import details:\n");

        String errorMessage = result.getErrorMessage();
        if (errorMessage != null) {
            stringBuilder.append(errorMessage).append("\n");
        }
        for (BuildImportResultRest buildImport : result.getBuilds()) {
            describeBuildImport(stringBuilder, buildImport);
        }

        return stringBuilder.toString();
    }

    private void describeBuildImport(StringBuilder stringBuilder, BuildImportResultRest buildImport) {
        Integer buildRecordId = buildImport.getBuildRecordId();
        BuildRecord record = orNull(buildRecordId, buildRecordRepository::queryById);
        BuildConfigurationAudited buildConfiguration = orNull(record, BuildRecord::getBuildConfigurationAudited); //TODO fix audited entity usage
        stringBuilder.append("\n-------------------------------------------------------------------------\n");
        String buildMessage =
                String.format("%s [buildRecordId: %d, built from %s rev %s] import %s. Brew build id: %d, Brew build url: %s\n",
                        orNull(buildConfiguration, BuildConfigurationAudited::getName),
                        orNull(record, BuildRecord::getId),
                        orNull(record, BuildRecord::getScmRepoURL),
                        orNull(record, BuildRecord::getScmRevision),
                        buildImport.getStatus(),
                        buildImport.getBrewBuildId(),
                        buildImport.getBrewBuildUrl());
        stringBuilder.append(buildMessage);
        if (buildImport.getStatus() != BuildImportStatus.SUCCESSFUL) {
            stringBuilder.append("Error message: ").append(buildImport.getErrorMessage());
            List<ArtifactImportError> errors = buildImport.getErrors();
            if (errors != null && !errors.isEmpty()) {
                errors.forEach(e -> describeArtifactImportError(stringBuilder, e));
            }
        }
        stringBuilder.append("\n");
    }

    private void describeArtifactImportError(StringBuilder stringBuilder, ArtifactImportError e) {
        Integer artifactId = e.getArtifactId();
        Artifact artifact = artifactRepository.queryById(artifactId);

        stringBuilder.append(
                String.format("Failed to import %s [artifactId:%d]. Error message: %s\n",
                        orNull(artifact, Artifact::getIdentifier),
                        artifactId,
                        e.getErrorMessage())
        );
    }

    /**
     * [NCL-3112] Mark the milestone provided as not current
     *
     * @param milestone ProductMilestone to not be current anymore
     */
    private void removeCurrentFlagFromMilestone(ProductMilestone milestone) {
        ProductVersion productVersion = milestone.getProductVersion();

        if (productVersion.getCurrentProductMilestone() != null &&
                productVersion.getCurrentProductMilestone().getId().equals(milestone.getId())) {

            productVersion.setCurrentProductMilestone(null);
            productVersionRepository.save(productVersion);
        }
    }

    private static <T, R> R orNull(T value, Function<T, R> f) {
        return value == null ? null : f.apply(value);
    }

}