/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.enricher.deprecated;

import com.google.common.io.Files;
import io.fabric8.kubernetes.api.builder.TypedVisitor;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ReplicationControllerBuilder;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.apps.DaemonSetBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.ReplicaSetBuilder;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
import io.fabric8.kubernetes.api.model.batch.JobBuilder;
import io.fabric8.maven.core.config.PlatformMode;
import io.fabric8.maven.core.util.Configs;
import io.fabric8.maven.core.util.FileUtil;
import io.fabric8.maven.core.util.SpringBootConfigurationHelper;
import io.fabric8.maven.enricher.api.BaseEnricher;
import io.fabric8.maven.enricher.api.MavenEnricherContext;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Map;

import io.fabric8.maven.enricher.api.util.ProjectClassLoaders;
import io.fabric8.openshift.api.model.DeploymentConfigBuilder;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * Enricher for adding icons to descriptors
 *
 * @author roland
 * @since 01/05/16
 */
public class IconEnricher extends BaseEnricher {

    private static String[] ICON_EXTENSIONS = new String[]{".svg", ".png", ".gif", ".jpg", ".jpeg"};

    private File templateTempDir;
    private File appConfigDir;
    private String iconBranch;

    // Available configuration keys
    private enum Config implements Configs.Key {

        templateTempDir,
        sourceDir,
        ref,
        maximumDataUrlSizeK   {{ d = "2"; }},
        urlPrefix,
        branch                {{ d = "master"; }},
        url;

        public String def() { return d; } protected String d;
    }

    public IconEnricher(MavenEnricherContext buildContext) {
        super(buildContext, "f8-deprecated-icon");

        String baseDir = getContext().getProjectDirectory().getAbsolutePath();
        templateTempDir = new File(getConfig(Config.templateTempDir, baseDir + "/target/fabric8/template-workdir"));
        appConfigDir = new File(getConfig(Config.sourceDir, baseDir + "/src/main/fabric8"));
    }

    public Map<String, String> getAnnotations(String kind) {
        String iconUrl = getIconUrl(extractIconRef());
        if (iconUrl != null) {
            log.info("Adding icon for %s", kind.toLowerCase());
            log.verbose("Icon URL: %s", iconUrl);
            return Collections.singletonMap("fabric8.io/iconUrl", iconUrl);
        } else {
            log.debug("No icon file found for resources of type " + kind);
        }
        return null;
    }

    @Override
    public void create(PlatformMode platformMode, KubernetesListBuilder builder) {
        builder.accept(new TypedVisitor<ServiceBuilder>() {
            @Override
            public void visit(ServiceBuilder serviceBuilder) {
                serviceBuilder.editMetadata().addToAnnotations(getAnnotations(serviceBuilder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<DeploymentBuilder>() {
            @Override
            public void visit(DeploymentBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<DeploymentConfigBuilder>() {
            @Override
            public void visit(DeploymentConfigBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<ReplicaSetBuilder>() {
            @Override
            public void visit(ReplicaSetBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<ReplicationControllerBuilder>() {
            @Override
            public void visit(ReplicationControllerBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<DaemonSetBuilder>() {
            @Override
            public void visit(DaemonSetBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<StatefulSetBuilder>() {
            @Override
            public void visit(StatefulSetBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });

        builder.accept(new TypedVisitor<JobBuilder>() {
            @Override
            public void visit(JobBuilder builder) {
                builder.editMetadata().addToAnnotations(getAnnotations(builder.getKind())).endMetadata();
            }
        });
    }

    // ====================================================================================================

    private String extractIconRef() {
        String iconRef = getConfig(Config.ref);
        if (StringUtils.isBlank(iconRef)) {
            iconRef = getDefaultIconRef();
        }
        return iconRef;
    }

    protected String getIconUrl(String iconRef) {
        String answer = getConfig(Config.url);
        if (StringUtils.isBlank(answer)) {
            try {
                if (templateTempDir != null) {
                    templateTempDir.mkdirs();
                    File iconFile = copyIconToFolder(iconRef, templateTempDir);
                    if (iconFile == null) {
                        copyAppConfigFiles(templateTempDir, appConfigDir);

                        // lets find the icon file...
                        for (String ext : ICON_EXTENSIONS) {
                            File file = new File(templateTempDir, "icon" + ext);
                            if (file.exists() && file.isFile()) {
                                iconFile = file;
                                break;
                            }
                        }
                    }
                    if (iconFile != null) {
                        answer = convertIconFileToURL(iconFile, iconRef);
                    }
                }
            } catch (Exception e) {
                log.warn("Failed to load icon file: %s", e);
            }
        }
        if (StringUtils.isBlank(answer)) {
            // maybe its a common icon that is embedded in fabric8-console
            if (StringUtils.isNotBlank(iconRef)) {
                String embeddedIcon = embeddedIconsInConsole(iconRef, "img/icons/");
                if (embeddedIcon != null) {
                    return embeddedIcon;
                } else {
                    log.warn("Could not resolve iconRef: %s", iconRef);
                }
            }
        }
        return answer;
    }

    /**
     * Lets use the project and its classpath to try figure out what default icon to use
     *
     * @return the icon ref if we can detect one or return null
     */
    private String getDefaultIconRef() {
        ProjectClassLoaders cls = getContext().getProjectClassLoaders();
        if (cls.isClassInCompileClasspath(false, "io.fabric8.funktion.runtime.Main") ||
            getContext().hasDependency( "io.fabric8.funktion", null)) {
            return "funktion";
        }
        if (cls.isClassInCompileClasspath(false, "org.apache.camel.CamelContext")) {
            return "camel";
        }
        if (getContext().hasPlugin(null, SpringBootConfigurationHelper.SPRING_BOOT_MAVEN_PLUGIN_ARTIFACT_ID)  ||
            cls.isClassInCompileClasspath(false, "org.springframework.boot.SpringApplication")) {
            return "spring-boot";
        }
        if (cls.isClassInCompileClasspath(false, "org.springframework.core.Constants")) {
            return "spring";
        }
        if (cls.isClassInCompileClasspath(false, "org.vertx.java.core.Handler", "io.vertx.core.Handler")) {
            return "vertx";
        }

        if (getContext().hasPlugin("org.wildfly.swarm", "wildfly-swarm-plugin") ||
            getContext().hasDependency( "org.wildfly.swarm", null)) {
            return "wildfly-swarm";
        }
        if (getContext().hasPlugin( "io.thorntail", "thorntail-maven-plugin") ||
            getContext().hasDependency( "io.thorntail", null)) {
            // use the WildFly Swarm icon until there's a dedicated Thorntail icon
            // Thorntail is a new name of WildFly Swarm
            return "wildfly-swarm";
        }

        return null;
    }

    private File copyIconToFolder(String iconRef, File appBuildDir) throws IOException {
        if (StringUtils.isNotBlank(iconRef)) {
            File[] icons = appBuildDir.listFiles((dir, name) -> {
                if (name == null) {
                    return false;
                }
                String lower = name.toLowerCase();
                if (lower.startsWith("icon.")) {
                    for (String ext : ICON_EXTENSIONS) {
                        if (lower.endsWith(ext)) {
                            return true;
                        }
                    }
                }
                return false;
            });
            if (icons == null || icons.length == 0) {
                // lets copy the iconRef
                InputStream in = loadPluginResource(iconRef);
                if (in == null) {
                    // maybe it dont have extension so try to find it
                    for (String ext : ICON_EXTENSIONS) {
                        String name = iconRef + ext;
                        in = loadPluginResource(name);
                        if (in != null) {
                            iconRef = name;
                            break;
                        }
                    }
                }
                if (in != null) {
                    String fileName = "icon." + Files.getFileExtension(iconRef);
                    File outFile = new File(appBuildDir, fileName);
                    FileUtils.copyInputStreamToFile(in, outFile);
                    log.info("Generated icon file " + outFile + " from icon reference: " + iconRef);
                    return outFile;
                }
            }
        }
        return null;
    }

    private InputStream loadPluginResource(String iconRef) {
        InputStream answer = Thread.currentThread().getContextClassLoader().getResourceAsStream(iconRef);
        if (answer == null) {
            answer = getContext().getProjectClassLoaders().getCompileClassLoader().getResourceAsStream(iconRef);
        }
        if (answer == null) {
            answer = this.getClass().getResourceAsStream(iconRef);
        }
        return answer;
    }

    /**
     * Copies any local configuration files into the app directory
     */
    private void copyAppConfigFiles(File appBuildDir, File appConfigDir) throws IOException {
        File[] files = appConfigDir.listFiles();
        if (files != null) {
            appBuildDir.mkdirs();
            for (File file : files) {
                File outFile = new File(appBuildDir, file.getName());
                if (file.isDirectory()) {
                    copyAppConfigFiles(outFile, file);
                } else {
                    Files.copy(file, outFile);
                }
            }
        }
    }

    private String convertIconFileToURL(File iconFile, String iconRef) throws IOException {
        long length = iconFile.length();

        int sizeK = Math.round(length / 1024);

        byte[] bytes = FileUtils.readFileToByteArray(iconFile);
        byte[] encoded = Base64.encodeBase64(bytes);

        int base64SizeK = Math.round(encoded.length / 1024);

        if (base64SizeK < Configs.asInt(getConfig(Config.maximumDataUrlSizeK))) {
            String mimeType = URLConnection.guessContentTypeFromName(iconFile.getName());
            return "data:" + mimeType + ";charset=UTF-8;base64," + new String(encoded);
        } else {
            File iconSourceFile = new File(appConfigDir, iconFile.getName());
            if (iconSourceFile.exists()) {
                // The next line was 'rootDir' and thats probably wrong now,
                // but then after all the icon enricher is not used anymore anyway,
                // so don't bother much with this.
                // We should remove the deprecated package asap.
                File rootProjectFolder = getContext().getProjectDirectory();
                if (rootProjectFolder != null) {
                    String urlPrefix = getConfig(Config.urlPrefix);
                    if (StringUtils.isBlank(urlPrefix)) {
                        String relativePath = FileUtil.getRelativePath(rootProjectFolder, iconSourceFile).toString();
                        String relativeParentPath =
                            FileUtil.getRelativePath(rootProjectFolder, getContext().getProjectDirectory()).toString();
                        if (getContext() instanceof MavenEnricherContext) {
                            MavenEnricherContext mavenEnricherContext = (MavenEnricherContext) getContext();
                            final org.apache.maven.model.Scm scm = mavenEnricherContext.getProject().getScm();
                            if (scm != null) {
                                String url = scm.getUrl();
                                if (url != null) {
                                    String[] prefixes = {"http://github.com/", "https://github.com/"};
                                    for (String prefix : prefixes) {
                                        if (url.startsWith(prefix)) {
                                            url = "https://cdn.rawgit.com/" + url.substring(prefix.length());
                                            break;
                                        }
                                    }
                                    if (url.endsWith(relativeParentPath)) {
                                        url = url.substring(0, url.length() - relativeParentPath.length());
                                    }
                                    urlPrefix = url;
                                }
                            }
                        }
                        if (StringUtils.isBlank(urlPrefix)) {
                            log.warn(
                                "No iconUrlPrefix defined or could be found via SCM in the pom.xml so cannot add an icon URL!");
                        } else {
                            return String.format("%s/%s/%s", urlPrefix, getConfig(Config.branch), relativePath);
                        }
                    }
                }
            } else {
                String embeddedIcon = embeddedIconsInConsole(iconRef, "img/icons/");
                if (embeddedIcon != null) {
                    return embeddedIcon;
                } else {
                    log.warn("Cannot find url for icon to use %s", iconRef);
                }
            }
        }
        return null;
    }

    /**
     * To use embedded icons provided by the fabric8-console
     *
     * @param iconRef  name of icon file
     * @param prefix   prefix location for the icons in the fabric8-console
     * @return the embedded icon ref, or <tt>null</tt> if no embedded icon found to be used
     */
    protected String embeddedIconsInConsole(String iconRef, String prefix) {
        if (iconRef == null) {
            return null;
        }

        if (iconRef.startsWith("icons/")) {
            iconRef = iconRef.substring(6);
        }

        // special for fabric8 as its in a different dir
        if (iconRef.contains("META-INF/fabric8")) {
            return "img/fabric8_icon.svg";
        }

        if (iconRef.contains("activemq")) {
            return prefix + "activemq.svg";
        } else if (iconRef.contains("apiman")) {
            return prefix + "apiman.png";
        } else if (iconRef.contains("api-registry")) {
            return prefix + "api-registry.svg";
        } else if (iconRef.contains("brackets")) {
            return prefix + "brackets.svg";
        } else if (iconRef.contains("camel")) {
            return prefix + "camel.svg";
        } else if (iconRef.contains("chaos-monkey")) {
            return prefix + "chaos-monkey.png";
        } else if (iconRef.contains("docker-registry")) {
            return prefix + "docker-registry.png";
        } else if (iconRef.contains("elasticsearch")) {
            return prefix + "elasticsearch.png";
        } else if (iconRef.contains("fluentd")) {
            return prefix + "fluentd.png";
        } else if (iconRef.contains("forge")) {
            return prefix + "forge.svg";
        } else if (iconRef.contains("funktion")) {
            return prefix + "funktion.png";
        } else if (iconRef.contains("gerrit")) {
            return prefix + "gerrit.png";
        } else if (iconRef.contains("gitlab")) {
            return prefix + "gitlab.svg";
        } else if (iconRef.contains("gogs")) {
            return prefix + "gogs.png";
        } else if (iconRef.contains("grafana")) {
            return prefix + "grafana.png";
        } else if (iconRef.contains("hubot-irc")) {
            return prefix + "hubot-irc.png";
        } else if (iconRef.contains("hubot-letschat")) {
            return prefix + "hubot-letschat.png";
        } else if (iconRef.contains("hubot-notifier")) {
            return prefix + "hubot-notifier.png";
        } else if (iconRef.contains("hubot-slack")) {
            return prefix + "hubot-slack.png";
        } else if (iconRef.contains("image-linker")) {
            return prefix + "image-linker.svg";
        } else if (iconRef.contains("javascript")) {
            return prefix + "javascript.png";
        } else if (iconRef.contains("java")) {
            return prefix + "java.svg";
        } else if (iconRef.contains("jenkins")) {
            return prefix + "jenkins.svg";
        } else if (iconRef.contains("jetty")) {
            return prefix + "jetty.svg";
        } else if (iconRef.contains("karaf")) {
            return prefix + "karaf.svg";
        } else if (iconRef.contains("keycloak")) {
            return prefix + "keycloak.svg";
        } else if (iconRef.contains("kibana")) {
            return prefix + "kibana.svg";
        } else if (iconRef.contains("kiwiirc")) {
            return prefix + "kiwiirc.png";
        } else if (iconRef.contains("letschat")) {
            return prefix + "letschat.png";
        } else if (iconRef.contains("mule")) {
            return prefix + "mule.svg";
        } else if (iconRef.contains("nexus")) {
            return prefix + "nexus.png";
        } else if (iconRef.contains("node")) {
            return prefix + "node.svg";
        } else if (iconRef.contains("orion")) {
            return prefix + "orion.png";
        } else if (iconRef.contains("prometheus")) {
            return prefix + "prometheus.png";
        } else if (iconRef.contains("django") || iconRef.contains("python")) {
            return prefix + "python.png";
        } else if (iconRef.contains("spring-boot")) {
            return prefix + "spring-boot.svg";
        } else if (iconRef.contains("taiga")) {
            return prefix + "taiga.png";
        } else if (iconRef.contains("tomcat")) {
            return prefix + "tomcat.svg";
        } else if (iconRef.contains("tomee")) {
            return prefix + "tomee.svg";
        } else if (iconRef.contains("vertx")) {
            return prefix + "vertx.svg";
        } else if (iconRef.contains("wildfly")) {
            return prefix + "wildfly.svg";
        } else if (iconRef.contains("wildfly-swarm")) {
            return prefix + "wildfly-swarm.png";
        } else if (iconRef.contains("weld")) {
            return prefix + "weld.svg";
        } else if (iconRef.contains("zipkin")) {
            return prefix + "zipkin.png";
        }

        return null;
    }

}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.core.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.SortedMap;

import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

/**
 * Utility methods to access spring-boot resources.
 */
public class SpringBootUtil {

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

    /**
     * Returns the spring boot configuration (supports `application.properties` and `application.yml`)
     * or an empty properties object if not found
     */
    public static Properties getSpringBootApplicationProperties(URLClassLoader compileClassLoader) {
        URL ymlResource = compileClassLoader.findResource("application.yml");
        URL propertiesResource = compileClassLoader.findResource("application.properties");

        Properties props = YamlUtil.getPropertiesFromYamlResource(ymlResource);
        props.putAll(getPropertiesResource(propertiesResource));
        return props;
    }

    /**
     * Returns the given properties resource on the project classpath if found or an empty properties object if not
     */
    protected static Properties getPropertiesResource(URL resource) {
        Properties answer = new Properties();
        if (resource != null) {
            try(InputStream stream = resource.openStream()) {
                answer.load(stream);
            } catch (IOException e) {
                throw new IllegalStateException("Error while reading resource from URL " + resource, e);
            }
        }
        return answer;
    }

    /**
     * Determine the spring-boot devtools version for the current project
     */
    public static Optional<String> getSpringBootDevToolsVersion(MavenProject mavenProject) {
        return getSpringBootVersion(mavenProject);
    }

    /**
     * Determine the spring-boot major version for the current project
     */
    public static Optional<String> getSpringBootVersion(MavenProject mavenProject) {
        return Optional.ofNullable(MavenUtil.getDependencyVersion(mavenProject, SpringBootConfigurationHelper.SPRING_BOOT_GROUP_ID, SpringBootConfigurationHelper.SPRING_BOOT_ARTIFACT_ID));
    }

}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.core.util;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;

/**
 * A simple factory for creating services with no-arg constructors from a textual
 * descriptor. This descriptor, which must be a resource loadable by this class'
 * classloader, is a plain text file which looks like
 *
 *

 *   com.example.MyProjectLabelEnricher
 *   !io.fabric8.maven.fabric8.enhancer.DefaultProjectLabelEnricher
 *   com.example.AnotherEnricher,50
 *

 *
 * If a line starts with ! it is removed if it has been added previously.
 * The optional second numeric value is the order in which the services are returned.
 *
 * @author roland
 * @since 05.11.10
 */
public final class PluginServiceFactory<C> {

    private List<ClassLoader> additionalClassLoaders = new ArrayList<>();

    // Parameters for service constructors
    private C context;

    public PluginServiceFactory(C context, ClassLoader ... loaders) {
        this.context = context;
        for (ClassLoader loader : loaders) {
            addAdditionalClassLoader(loader);
        }
    }

    /**
     * Create a list of services ordered according to the ordering given in the
     * service descriptor files. Note, that the descriptor will be looked up
     * in the whole classpath space, which can result in reading in multiple
     * descriptors with a single path. Note, that the reading order for multiple
     * resources with the same name is not defined.
     *
     * @param descriptorPaths a list of resource paths which are handle in the given order.
     *        Normally, default service should be given as first parameter so that custom
     *        descriptors have a chance to remove a default service.
     * @param <T> type of the service objects to create
     * @return a ordered list of created services or an empty list.
     */
    public <T> List<T> createServiceObjects(String... descriptorPaths) {
        try {
            ServiceEntry.initDefaultOrder();
            TreeMap<ServiceEntry,T> serviceMap = new TreeMap<ServiceEntry,T>();
            for (String descriptor : descriptorPaths) {
                readServiceDefinitions(serviceMap, descriptor);
            }
            ArrayList<T> ret = new ArrayList<T>();
            for (T service : serviceMap.values()) {
                ret.add(service);
            }
            return ret;
        } finally {
            ServiceEntry.removeDefaultOrder();
        }
    }

    private <T> void readServiceDefinitions(Map<ServiceEntry, T> extractorMap, String defPath) {
        try {
            for (String url : ClassUtil.getResources(defPath, additionalClassLoaders)) {
                readServiceDefinitionFromUrl(extractorMap, url);
            }
        } catch (IOException e) {
            throw new IllegalStateException("Cannot load service from " + defPath + ": " + e, e);
        }
    }

    private <T> void readServiceDefinitionFromUrl(Map<ServiceEntry, T> extractorMap, String url) {
        String line = null;
        try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(new URL(url).openStream(), "UTF8"))) {
            line = reader.readLine();
            while (line != null) {
                createOrRemoveService(extractorMap, line);
                line = reader.readLine();
            }
        } catch (ReflectiveOperationException|IOException e) {
            throw new IllegalStateException("Cannot load service " + line + " defined in " +
                                            url + " : " + e + ". Aborting", e);
        }
    }

    // Matches comment lines and empty lines. these are skipped
    private static Pattern COMMENT_LINE_PATTERN = Pattern.compile("^(\\s*#.*|\\s*)$");

    private synchronized  <T> void createOrRemoveService(Map<ServiceEntry, T> serviceMap, String line)
        throws ReflectiveOperationException {
        if (line.length() > 0 && !COMMENT_LINE_PATTERN.matcher(line).matches()) {
            ServiceEntry entry = new ServiceEntry(line);
            if (entry.isRemove()) {
                // Removing is a bit complex since we need to find out
                // the proper key since the order is part of equals/hash
                // so we cant fetch/remove it directly
                Set<ServiceEntry> toRemove = new HashSet<ServiceEntry>();
                for (ServiceEntry key : serviceMap.keySet()) {
                    if (key.getClassName().equals(entry.getClassName())) {
                        toRemove.add(key);
                    }
                }
                for (ServiceEntry key : toRemove) {
                    serviceMap.remove(key);
                }
            } else {
                Class<T> clazz = ClassUtil.classForName(entry.getClassName(), additionalClassLoaders);
                if (clazz == null) {
                    throw new ClassNotFoundException("Class " + entry.getClassName() + " could not be found");
                }
                Constructor<T> constructor = clazz.getConstructor(context.getClass());
                if (constructor == null) {
                    throw new IllegalArgumentException(
                        "Internal Error: " + clazz + " does not have constructor (" + context.getClass() + ")");
                }
                T service = constructor.newInstance(context);
                serviceMap.put(entry, service);
            }
        }
    }

    public void addAdditionalClassLoader(ClassLoader classLoader) {
        this.additionalClassLoaders.add(classLoader);
    }

    // =============================================================================

     static class ServiceEntry implements Comparable<ServiceEntry> {
        private String className;
        private boolean remove;
        private Integer order;

        private static ThreadLocal<Integer> defaultOrderHolder = new ThreadLocal<Integer>() {

            /**
             * Initialise with start value for entries without an explicite order. 100 in this case.
             *
             * @return 100
             */
            @Override
            protected Integer initialValue() {
                return Integer.valueOf(100);
            }
        };

        /**
         * Parse an entry in the service definition. This should be the full qualified classname
         * of a service, optional prefixed with "!" in which case the service is removed
         * from the defaul list. An order value can be appened after the classname with a comma for give a
         * indication for the ordering of services. If not given, 100 is taken for the first entry, counting up.
         *
         * @param line line to parse
         */
        public ServiceEntry(String line) {
            String[] parts = line.split(",");
            if (parts[0].startsWith("!")) {
                remove = true;
                className = parts[0].substring(1);
            } else {
                remove = false;
                className = parts[0];
            }
            if (parts.length > 1) {
                try {
                    order = Integer.parseInt(parts[1]);
                } catch (NumberFormatException exp) {
                    order = nextDefaultOrder();
                }
            } else {
                order = nextDefaultOrder();
            }
        }

        private Integer nextDefaultOrder() {
            Integer defaultOrder = defaultOrderHolder.get();
            defaultOrderHolder.set(defaultOrder + 1);
            return defaultOrder;
        }

        private static void initDefaultOrder() {
            defaultOrderHolder.set(100);
        }

        private static void removeDefaultOrder() {
            defaultOrderHolder.remove();
        }

        private String getClassName() {
            return className;
        }

        private boolean isRemove() {
            return remove;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) { return true; }
            if (o == null || getClass() != o.getClass()) { return false; }

            ServiceEntry that = (ServiceEntry) o;

            return className.equals(that.className);

        }

        @Override
        public int hashCode() {
            return className.hashCode();
        }

        /** {@inheritDoc} */
        public int compareTo(ServiceEntry o) {
            int ret = this.order - o.order;
            return ret != 0 ? ret : this.className.compareTo(o.className);
        }
    }
}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.plugin.enricher;

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

import com.google.common.base.Function;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.maven.core.config.PlatformMode;
import io.fabric8.maven.core.config.ProcessorConfig;
import io.fabric8.maven.core.config.ResourceConfig;
import io.fabric8.maven.core.util.ClassUtil;
import io.fabric8.maven.core.util.PluginServiceFactory;
import io.fabric8.maven.docker.util.Logger;
import io.fabric8.maven.enricher.api.Enricher;
import io.fabric8.maven.enricher.api.EnricherContext;

import static io.fabric8.maven.enricher.api.util.Misc.filterEnrichers;

/**
 * @author roland
 * @since 08/04/16
 */
public class EnricherManager {

    // List of enrichers used for customizing the generated deployment descriptors
    private List<Enricher> enrichers;

    // context used by enrichers
    private final ProcessorConfig defaultEnricherConfig;

    private Logger log;

    public EnricherManager(ResourceConfig resourceConfig, EnricherContext enricherContext, Optional<List<String>> extraClasspathElements) {
        PluginServiceFactory<EnricherContext> pluginFactory = new PluginServiceFactory<>(enricherContext);

        extraClasspathElements.ifPresent(
                cpElements -> pluginFactory.addAdditionalClassLoader(ClassUtil.createProjectClassLoader(cpElements, enricherContext.getLog())));

        this.log = enricherContext.getLog();
        this.defaultEnricherConfig = enricherContext.getConfiguration().getProcessorConfig().orElse(ProcessorConfig.EMPTY);

        this.enrichers = pluginFactory.createServiceObjects("META-INF/fabric8-enricher-default",
                "META-INF/fabric8/enricher-default",
                "META-INF/fabric8-enricher",
                "META-INF/fabric8/enricher");

        logEnrichers(filterEnrichers(defaultEnricherConfig, enrichers));

    }

    public void createDefaultResources(PlatformMode platformMode, final KubernetesListBuilder builder) {
        createDefaultResources(platformMode, defaultEnricherConfig, builder);
    }

    public void createDefaultResources(PlatformMode platformMode, ProcessorConfig enricherConfig, final KubernetesListBuilder builder) {
        // Add default resources
        loop(enricherConfig, enricher -> {
            enricher.create(platformMode, builder);
            return null;
        });
    }

    public void enrich(PlatformMode platformMode, KubernetesListBuilder builder) {
        enrich(platformMode, defaultEnricherConfig, builder);
    }

    public void enrich(PlatformMode platformMode, ProcessorConfig config, KubernetesListBuilder builder) {
        enrich(platformMode, config, builder, enrichers);
    }

    /**
     * Allow enricher to add Metadata to the resources.
     *
     * @param builder builder to customize
     * @param enricherList list of enrichers
     */
    private void enrich(PlatformMode platformMode, final ProcessorConfig enricherConfig, final KubernetesListBuilder builder, final List<Enricher> enricherList) {
        loop(enricherConfig, enricher -> {
                enricher.enrich(platformMode, builder);
                return null;
            });
    }

    // =============================================================================================
    private void logEnrichers(List<Enricher> enrichers) {
        log.verbose("Enrichers:");
        for (Enricher enricher : enrichers) {
            log.verbose("- %s", enricher.getName());
        }
    }

    private void loop(ProcessorConfig config, Function<Enricher, Void> function) {
        for (Enricher enricher : filterEnrichers(config, enrichers)) {
            function.apply(enricher);
        }
    }
}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.core.util;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.Properties;

public class ThorntailUtil {

    /**
     * Returns the thorntail configuration (supports `project-defaults.yml`)
     * or an empty properties object if not found
     */
    public static Properties getThorntailProperties(URLClassLoader compileClassLoader) {
        URL ymlResource = compileClassLoader.findResource("project-defaults.yml");

        Properties props = YamlUtil.getPropertiesFromYamlResource(ymlResource);
        return props;
    }
}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.enricher.api;

import java.util.Map;

import io.fabric8.maven.core.config.ProcessorConfig;
import io.fabric8.maven.core.model.Configuration;
import io.fabric8.maven.core.util.Configs;

/**
 * @author roland
 * @since 24/05/16
 */
public class EnricherConfig {

    private static final String ENRICHER_PROP_PREFIX = "fabric8.enricher";

    private final String name;
    private final Configuration configuration;

    public EnricherConfig(String name, Configuration configuration) {
        this.name = name;
        this.configuration = configuration;
    }

    /**
     * Get a configuration value
     *
     * @param key key to lookup.
     * @return the configuration value
     */
    public String get(Configs.Key key) {
        return get(key, key.def());
    }

    /**
     * Get the raw, untyped configuration or an empty map
     * @return raw configuration.
     */
    public Map<String, String> getRawConfig() {
        return configuration.getProcessorConfig().orElse(ProcessorConfig.EMPTY).getConfigMap(name);
    }

    /**
     * Get a config value with a default. If no value is given, as a last resort, project properties are looked up.
     *
     * @param key key part to lookup. The whole key is build up from prefix + "." + key. If key is null,
     *            then only the prefix is used for the lookup (this is suitable for enrichers having only one config option)
     * @param defaultVal the default value to use when the no config is set
     * @return the value looked up or the default value.
     */
    public String get(Configs.Key key, String defaultVal) {
        String val = configuration.getProcessorConfig().orElse(ProcessorConfig.EMPTY).getConfig(name, key.name());

        if (val == null) {
            String fullKey = ENRICHER_PROP_PREFIX + "." + name + "." + key;
            val = configuration.getPropertyWithSystemOverride(fullKey);
        }
        return val != null ? val : defaultVal;
    }

}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.core.util.validator;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import javax.validation.metadata.ConstraintDescriptor;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.ValidationMessage;
import io.fabric8.maven.core.util.ResourceClassifier;
import io.fabric8.maven.docker.util.Logger;

/**
 * Validates Kubernetes/OpenShift resource descriptors using JSON schema validation method.
 * For Openshift it adds some some exceptions from JSON schema constraints and ignores some validation errors.
 */

public class ResourceValidator {

    public static final String SCHEMA_JSON = "/schema/kube-validation-schema.json";
    private Logger log;
    private File resources[];
    private ResourceClassifier target = ResourceClassifier.KUBERNETES;
    private List<ValidationRule> ignoreValidationRules = new ArrayList<>();

    /**
     * @param inputFile File/Directory path of resource descriptors
     */
    public ResourceValidator(File inputFile) {
        if(inputFile.isDirectory()) {
            resources = inputFile.listFiles();
        } else {
            resources = new File[]{inputFile};
        }
    }

    /**
     * @param inputFile File/Directory path of resource descriptors
     * @param target  Target platform e.g OpenShift, Kubernetes
     * @param log Logger for logging messages on standard output devices
     */
    public ResourceValidator(File inputFile, ResourceClassifier target, Logger log) {
        this(inputFile);
        this.target = target;
        this.log = log;
        setupIgnoreRules(this.target);
    }

    /*
     * Add exception rules to ignore validation constraint from JSON schema for OpenShift/Kubernetes resources. Some fields in JSON schema which are marked as required
     * but in reality it's not required to provide values for those fields while creating the resources.
     * e.g. In DeploymentConfig(https://docs.openshift.com/container-platform/3.6/rest_api/openshift_v1.html#v1-deploymentconfig) model 'status' field is marked as
     * required.
     */
    private void setupIgnoreRules(ResourceClassifier target) {
        ignoreValidationRules.add(new IgnorePortValidationRule(IgnorePortValidationRule.TYPE));
        ignoreValidationRules.add(new IgnoreResourceMemoryLimitRule(IgnoreResourceMemoryLimitRule.TYPE));
    }

    /**
     * Validates the resource descriptors as per JSON schema. If any resource is invalid it throws @{@link ConstraintViolationException} with
     * all violated constraints
     *
     * @return number of resources processed
     * @throws ConstraintViolationException
     * @throws IOException
     */
    public int validate() throws ConstraintViolationException, IOException {
        for(File resource: resources) {
            if (resource.isFile() && resource.exists()) {
                try {
                    log.info("validating %s resource", resource.toString());
                    JsonNode inputSpecNode = geFileContent(resource);
                    String kind = inputSpecNode.get("kind").toString();
                    JsonSchema schema = getJsonSchema(prepareSchemaUrl(SCHEMA_JSON), kind);
                    Set<ValidationMessage> errors = schema.validate(inputSpecNode);
                    processErrors(errors, resource);
                } catch (URISyntaxException e) {
                    throw new IOException(e);
                }
            }
        }

        return resources.length;
    }

    private void processErrors(Set<ValidationMessage> errors, File resource) {
        Set<ConstraintViolationImpl> constraintViolations = new HashSet<>();
        for (ValidationMessage errorMsg: errors) {
            if(!ignoreError(errorMsg))
                constraintViolations.add(new ConstraintViolationImpl(errorMsg));
        }

        if(constraintViolations.size() > 0) {
            throw new ConstraintViolationException(getErrorMessage(resource, constraintViolations), constraintViolations);
        }
    }

    private boolean ignoreError(ValidationMessage errorMsg) {
        for (ValidationRule rule : ignoreValidationRules) {
            if(rule.ignore(errorMsg)) {
                return  true;
            }
        }

        return false;
    }

    private String getErrorMessage(File resource, Set<ConstraintViolationImpl> violations) {
        StringBuilder validationError = new StringBuilder();
        validationError.append("Invalid Resource : ");
        validationError.append(resource.toString());

        for (ConstraintViolationImpl violation: violations) {
            validationError.append("\n");
            validationError.append(violation.toString());
        }

        return  validationError.toString();
    }

    private JsonSchema getJsonSchema(URI schemaUrl, String kind) throws IOException {
        checkIfKindPropertyExists(kind);
        JsonSchemaFactory factory = new JsonSchemaFactory();
        JsonObject jsonSchema = getSchemaJson(schemaUrl);
        getResourceProperties(kind, jsonSchema);

        return factory.getSchema(jsonSchema.toString());
    }

    private void getResourceProperties(String kind, JsonObject jsonSchema) {
        jsonSchema.add("properties" , jsonSchema.get("resources").getAsJsonObject()
                .getAsJsonObject(kind.replaceAll("\"", "").toLowerCase())
                .getAsJsonObject("properties"));
    }

    private void checkIfKindPropertyExists(String kind) {
        if(kind == null) {
            throw new JsonIOException("Invalid kind of resource or 'kind' is missing from resource definition");
        }
    }

    private URI prepareSchemaUrl(String schemaFile) throws URISyntaxException {
        return getClass().getResource(schemaFile).toURI();
    }

    private JsonNode geFileContent(File file) throws IOException {
        try (InputStream resourceStream = new FileInputStream(file)) {
            ObjectMapper jsonMapper = new ObjectMapper(new YAMLFactory());
            return jsonMapper.readTree(resourceStream);
        }
    }

    public JsonObject getSchemaJson(URI schemaUrl) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String rootNode = objectMapper.readValue(schemaUrl.toURL(), JsonNode.class).toString();
        JsonObject jsonObject = new JsonParser().parse(rootNode).getAsJsonObject();
        jsonObject.remove("id");
        return jsonObject;
    }

    private class ConstraintViolationImpl implements ConstraintViolation<ValidationMessage> {

        private ValidationMessage errorMsg;

        public ConstraintViolationImpl(ValidationMessage errorMsg) {
            this.errorMsg = errorMsg;
        }

        @Override
        public String getMessage() {
            return errorMsg.getMessage();
        }

        @Override
        public String getMessageTemplate() {
            return null;
        }

        @Override
        public ValidationMessage getRootBean() {
            return null;
        }

        @Override
        public Class<ValidationMessage> getRootBeanClass() {
            return null;
        }

        @Override
        public Object getLeafBean() {
            return null;
        }

        @Override
        public Object[] getExecutableParameters() {
            return new Object[0];
        }

        @Override
        public Object getExecutableReturnValue() {
            return null;
        }

        @Override
        public Path getPropertyPath() {
            return null;
        }

        @Override
        public Object getInvalidValue() {
            return null;
        }

        @Override
        public ConstraintDescriptor<?> getConstraintDescriptor() {
            return null;
        }

        @Override
        public <U> U unwrap(Class<U> aClass) {
            return null;
        }

        @Override
        public String toString() {
            return "[message=" + getMessage().replaceFirst("[$]", "") +", violation type="+errorMsg.getType()+"]";
        }
    }
}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.core.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import org.yaml.snakeyaml.Yaml;

public class YamlUtil {
    protected static Properties getPropertiesFromYamlResource(URL resource) {
        if (resource != null) {
            try (InputStream yamlStream = resource.openStream()) {
                Yaml yaml = new Yaml();
                @SuppressWarnings("unchecked")
                SortedMap<String, Object> source = yaml.loadAs(yamlStream, SortedMap.class);
                Properties properties = new Properties();
                if (source != null) {
                    try {
                        properties.putAll(getFlattenedMap(source));
                    } catch (IllegalArgumentException e) {
                        throw new IllegalArgumentException(String.format("Spring Boot configuration file %s is not formatted correctly. %s",
                            resource.toString(), e.getMessage()));
                    }
                }
                return properties;
            } catch (IOException e) {
                throw new IllegalStateException("Error while reading Yaml resource from URL " + resource, e);
            }
        }
        return new Properties();
    }

    /**
     * Build a flattened representation of the Yaml tree. The conversion is compliant with the thorntail spring-boot rules.
     */
    private static Map<String, Object> getFlattenedMap(Map<String, Object> source) {
        Map<String, Object> result = new LinkedHashMap<>();
        buildFlattenedMap(result, source, null);
        return result;
    }

    @SuppressWarnings("unchecked")
    private static void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
        for (Map.Entry<String, Object> entry : source.entrySet()) {
            Object keyObject = entry.getKey();

            // If user creates a wrong application.yml then we get a runtime classcastexception
            if (!(keyObject instanceof String)) {
                throw new IllegalArgumentException(String.format("Expected to find a key of type String but %s with content %s found.",
                    keyObject.getClass(), keyObject.toString()));
            }

            String key = (String) keyObject;

            if (path !=null && path.trim().length()>0) {
                if (key.startsWith("[")) {
                    key = path + key;
                }
                else {
                    key = path + "." + key;
                }
            }
            Object value = entry.getValue();
            if (value instanceof Map) {

                Map<String, Object> map = (Map<String, Object>) value;
                buildFlattenedMap(result, map, key);
            }
            else if (value instanceof Collection) {
                Collection<Object> collection = (Collection<Object>) value;
                int count = 0;
                for (Object object : collection) {
                    buildFlattenedMap(result,
                        Collections.singletonMap("[" + (count++) + "]", object), key);
                }
            }
            else {
                result.put(key, (value != null ? value.toString() : ""));
            }
        }
    }

}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.core.model;

public class GroupArtifactVersion {

    private static final String PREFIX = "s";

    private String groupId;
    private String artifactId;
    private String version;

    public GroupArtifactVersion(String groupId, String artifactId, String version) {
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.version = version;
    }

    public String getGroupId() {
        return groupId;
    }

    public String getArtifactId() {
        return artifactId;
    }

    public String getVersion() {
        return version;
    }

    public boolean isSnapshot() {
        return getVersion() != null && getVersion().endsWith("SNAPSHOT");
    }

    /**
     * ArtifactId is used for setting a resource name (service, pod,...) in Kubernetes resource.
     * The problem is that a Kubernetes resource name must start by a char.
     * This method returns a valid string to be used as Kubernetes name.
     * @return Sanitized Kubernetes name.
     */
    public String getSanitizedArtifactId() {
        if (this.artifactId != null && !this.artifactId.isEmpty() && Character.isDigit(this.artifactId.charAt(0))) {
            return PREFIX + this.artifactId;
        }

        return this.artifactId;
    }
}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.plugin.mojo.develop;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.maven.core.access.ClusterAccess;
import io.fabric8.maven.core.access.ClusterConfiguration;
import io.fabric8.maven.core.config.OpenShiftBuildStrategy;
import io.fabric8.maven.core.config.RuntimeMode;
import io.fabric8.maven.core.config.ProcessorConfig;
import io.fabric8.maven.core.service.Fabric8ServiceHub;
import io.fabric8.maven.core.util.ProfileUtil;
import io.fabric8.maven.core.util.kubernetes.KubernetesResourceUtil;
import io.fabric8.maven.core.util.kubernetes.OpenshiftHelper;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.docker.service.BuildService;
import io.fabric8.maven.docker.service.ServiceHub;
import io.fabric8.maven.docker.service.WatchService;
import io.fabric8.maven.docker.util.AnsiLogger;
import io.fabric8.maven.docker.util.Logger;
import io.fabric8.maven.generator.api.GeneratorContext;
import io.fabric8.maven.generator.api.GeneratorMode;
import io.fabric8.maven.plugin.generator.GeneratorManager;
import io.fabric8.maven.plugin.mojo.ResourceDirCreator;
import io.fabric8.maven.plugin.watcher.WatcherManager;
import io.fabric8.maven.watcher.api.WatcherContext;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Set;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.repository.RepositorySystem;

import static io.fabric8.maven.plugin.mojo.build.ApplyMojo.DEFAULT_KUBERNETES_MANIFEST;
import static io.fabric8.maven.plugin.mojo.build.ApplyMojo.DEFAULT_OPENSHIFT_MANIFEST;

// TODO: Similar to the DebugMojo the WatchMojo should scale down any deployment to 1 replica (or ensure that its running only with one replica)
// The WatchEnricher has been removed since the enrichment shouldn't know anything about the mode running and should
// always create the same resources

/**
 * Used to automatically rebuild Docker images and restart containers in case of updates.
 */
@Mojo(name = "watch", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE)
@Execute(goal = "deploy")
public class WatchMojo extends io.fabric8.maven.docker.WatchMojo {

    @Parameter
    ProcessorConfig generator;

    /**
     * To skip over the execution of the goal
     */
    @Parameter(property = "fabric8.skip", defaultValue = "false")
    protected boolean skip;
    /**
     * The generated kubernetes YAML file
     */
    @Parameter(property = "fabric8.kubernetesManifest", defaultValue = DEFAULT_KUBERNETES_MANIFEST)
    private File kubernetesManifest;
    /**
     * The generated openshift YAML file
     */
    @Parameter(property = "fabric8.openshiftManifest", defaultValue = DEFAULT_OPENSHIFT_MANIFEST)
    private File openshiftManifest;
    /**
     * Whether to perform a Kubernetes build (i.e. against a vanilla Docker daemon) or
     * an OpenShift build (with a Docker build against the OpenShift API server.
     */
    @Parameter(property = "fabric8.mode")
    private RuntimeMode mode = RuntimeMode.auto;
    /**
     * OpenShift build mode when an OpenShift build is performed.
     * Can be either "s2i" for an s2i binary build mode or "docker" for a binary
     * docker mode.
     */
    @Parameter(property = "fabric8.build.strategy")
    private OpenShiftBuildStrategy buildStrategy = OpenShiftBuildStrategy.s2i;

    @Parameter
    protected ClusterConfiguration access;

    /**
     * Watcher specific options. This is a generic prefix where the keys have the form
     * <watcher-prefix>-<option>.
     */
    @Parameter
    private ProcessorConfig watcher;

    /**
     * Should we use the project's compile-time classpath to scan for additional enrichers/generators?
     */
    @Parameter(property = "fabric8.useProjectClasspath", defaultValue = "false")
    private boolean useProjectClasspath = false;

    /**
     * Profile to use. A profile contains the enrichers and generators to
     * use as well as their configuration. Profiles are looked up
     * in the classpath and can be provided as yaml files.
     *
     * However, any given enricher and or generator configuration overrides
     * the information provided by a profile.
     */
    @Parameter(property = "fabric8.profile")
    private String profile;

    /**
     * Folder where to find project specific files, e.g a custom profile
     */
    @Parameter(property = "fabric8.resourceDir", defaultValue = "${basedir}/src/main/fabric8")
    private File resourceDir;

    /**
     * Environment name where resources are placed. For example, if you set this property to dev and resourceDir is the default one, Fabric8 will look at src/main/fabric8/dev
     */
    @Parameter(property = "fabric8.environment")
    private String environment;

    // Whether to use color
    @Parameter(property = "fabric8.useColor", defaultValue = "true")
    protected boolean useColor;

    // For verbose output
    @Parameter(property = "fabric8.verbose", defaultValue = "false")
    protected boolean verbose;

    @Component
    protected RepositorySystem repositorySystem;

    private ClusterAccess clusterAccess;
    private KubernetesClient kubernetes;
    private ServiceHub hub;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (skip) {
            return;
        }

        log = new AnsiLogger(getLog(), useColor, verbose, !settings.getInteractiveMode(), getLogPrefix());
        clusterAccess = new ClusterAccess(getClusterConfiguration());
        kubernetes = clusterAccess.createDefaultClient(log);

        if(clusterAccess.resolveRuntimeMode(mode, log).equals(RuntimeMode.kubernetes)) {
            super.execute();
        } else {
            executeInternal(null);
        }
    }

    protected ClusterConfiguration getClusterConfiguration() {
        if(access == null) {
            access = new ClusterConfiguration.Builder().build();
        }
        final ClusterConfiguration.Builder clusterConfigurationBuilder = new ClusterConfiguration.Builder(access);

        return clusterConfigurationBuilder.from(System.getProperties())
            .from(project.getProperties()).build();
    }

    @Override
    protected synchronized void executeInternal(ServiceHub hub) throws MojoExecutionException {
        if(hub != null) {
            this.hub = hub;
        }

        URL masterUrl = kubernetes.getMasterUrl();
        KubernetesResourceUtil.validateKubernetesMasterUrl(masterUrl);

        File manifest;
        boolean isOpenshift = OpenshiftHelper.isOpenShift(kubernetes);
        if (isOpenshift) {
            manifest = openshiftManifest;
        } else {
            manifest = kubernetesManifest;
        }

        try {
            Set<HasMetadata> resources = KubernetesResourceUtil.loadResources(manifest);
            WatcherContext context = getWatcherContext();

            WatcherManager.watch(getResolvedImages(), resources, context);

        } catch (KubernetesClientException ex) {
            KubernetesResourceUtil.handleKubernetesClientException(ex, this.log);
        } catch (Exception ex) {
            throw new MojoExecutionException("An error has occurred while while trying to watch the resources", ex);
        }

    }

    public WatcherContext getWatcherContext() throws MojoExecutionException {
        try {
            BuildService.BuildContext buildContext = getBuildContext();
            WatchService.WatchContext watchContext = hub != null ? getWatchContext(hub) : null;

            return new WatcherContext.Builder()
                    .serviceHub(hub)
                    .buildContext(buildContext)
                    .watchContext(watchContext)
                    .config(extractWatcherConfig())
                    .logger(log)
                    .newPodLogger(createLogger("[[C]][NEW][[C]] "))
                    .oldPodLogger(createLogger("[[R]][OLD][[R]] "))
                    .mode(mode)
                    .project(project)
                    .useProjectClasspath(useProjectClasspath)
                    .clusterConfiguration(getClusterConfiguration())
                    .kubernetesClient(kubernetes)
                    .fabric8ServiceHub(getFabric8ServiceHub())
                    .build();
        } catch(IOException exception) {
            throw new MojoExecutionException(exception.getMessage());
        }
    }

    protected Fabric8ServiceHub getFabric8ServiceHub() {
        return new Fabric8ServiceHub.Builder()
                .log(log)
                .clusterAccess(clusterAccess)
                .dockerServiceHub(hub)
                .platformMode(mode)
                .repositorySystem(repositorySystem)
                .mavenProject(project)
                .build();
    }

    @Override
    public List<ImageConfiguration> customizeConfig(List<ImageConfiguration> configs) {
        try {
            Fabric8ServiceHub serviceHub = getFabric8ServiceHub();
            GeneratorContext ctx = new GeneratorContext.Builder()
                    .config(extractGeneratorConfig())
                    .project(project)
                    .logger(log)
                    .runtimeMode(mode)
                    .strategy(buildStrategy)
                    .useProjectClasspath(useProjectClasspath)
                    .artifactResolver(serviceHub.getArtifactResolverService())
                    .generatorMode(GeneratorMode.WATCH)
                    .build();
            return GeneratorManager.generate(configs, ctx, false);
        } catch (MojoExecutionException e) {
            throw new IllegalArgumentException("Cannot extract generator config: " + e, e);
        }
    }

    // Get watcher config
    private ProcessorConfig extractWatcherConfig() {
        try {
            return ProfileUtil.blendProfileWithConfiguration(ProfileUtil.WATCHER_CONFIG, profile, ResourceDirCreator.getFinalResourceDir(resourceDir, environment), watcher);
        } catch (IOException e) {
            throw new IllegalArgumentException("Cannot extract watcher config: " + e, e);
        }
    }

    // Get generator config
    private ProcessorConfig extractGeneratorConfig() {
        try {
            return ProfileUtil.blendProfileWithConfiguration(ProfileUtil.GENERATOR_CONFIG, profile, ResourceDirCreator.getFinalResourceDir(resourceDir, environment), generator);
        } catch (IOException e) {
            throw new IllegalArgumentException("Cannot extract generator config: " + e, e);
        }
    }

    protected Logger createLogger(String prefix) {
        return new AnsiLogger(getLog(), useColor, verbose, !settings.getInteractiveMode(), "F8:" + prefix);
    }

    @Override
    protected String getLogPrefix() {
        return "F8: ";
    }
}


/**
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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 io.fabric8.maven.plugin.mojo.build;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.maven.core.access.ClusterAccess;
import io.fabric8.maven.core.access.ClusterConfiguration;
import io.fabric8.maven.core.config.MappingConfig;
import io.fabric8.maven.core.config.OpenShiftBuildStrategy;
import io.fabric8.maven.core.config.PlatformMode;
import io.fabric8.maven.core.config.ProcessorConfig;
import io.fabric8.maven.core.config.Profile;
import io.fabric8.maven.core.config.ResourceConfig;
import io.fabric8.maven.core.config.RuntimeMode;
import io.fabric8.maven.core.handler.HandlerHub;
import io.fabric8.maven.core.util.FileUtil;
import io.fabric8.maven.core.util.MavenUtil;
import io.fabric8.maven.core.util.ProfileUtil;
import io.fabric8.maven.core.util.ResourceClassifier;
import io.fabric8.maven.core.util.ResourceFileType;
import io.fabric8.maven.core.util.ResourceUtil;
import io.fabric8.maven.core.util.ValidationUtil;
import io.fabric8.maven.core.util.kubernetes.KubernetesHelper;
import io.fabric8.maven.core.util.kubernetes.KubernetesResourceUtil;
import io.fabric8.maven.core.util.validator.ResourceValidator;
import io.fabric8.maven.docker.AbstractDockerMojo;
import io.fabric8.maven.docker.config.ConfigHelper;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.docker.config.handler.ImageConfigResolver;
import io.fabric8.maven.docker.util.EnvUtil;
import io.fabric8.maven.docker.util.ImageNameFormatter;
import io.fabric8.maven.docker.util.Logger;
import io.fabric8.maven.enricher.api.MavenEnricherContext;
import io.fabric8.maven.generator.api.GeneratorContext;
import io.fabric8.maven.plugin.enricher.EnricherManager;
import io.fabric8.maven.plugin.generator.GeneratorManager;
import io.fabric8.maven.plugin.mojo.AbstractFabric8Mojo;
import io.fabric8.maven.plugin.mojo.ResourceDirCreator;
import io.fabric8.openshift.api.model.Template;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.filtering.MavenFileFilter;
import org.apache.maven.shared.filtering.MavenFilteringException;

import javax.validation.ConstraintViolationException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import static io.fabric8.maven.core.util.ResourceFileType.yaml;
import static io.fabric8.maven.plugin.mojo.build.ApplyMojo.DEFAULT_OPENSHIFT_MANIFEST;

/**
 * Generates or copies the Kubernetes JSON file and attaches it to the build so its
 * installed and released to maven repositories like other build artifacts.
 */
@Mojo(name = "resource", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, requiresDependencyResolution = ResolutionScope.COMPILE)
public class ResourceMojo extends AbstractFabric8Mojo {
    /**
     * Used to annotate a resource as being for a specific platform only such as "kubernetes" or "openshift"
     */

    // THe key how we got the the docker maven plugin
    private static final String DOCKER_MAVEN_PLUGIN_KEY = "io.fabric8:docker-maven-plugin";
    private static final String DOCKER_IMAGE_USER = "docker.image.user";
    /**
     * The generated kubernetes and openshift manifests
     */
    @Parameter(property = "fabric8.targetDir", defaultValue = "${project.build.outputDirectory}/META-INF/fabric8")
    protected File targetDir;

    @Component(role = MavenFileFilter.class, hint = "default")
    private MavenFileFilter mavenFileFilter;

    @Component
    private ImageConfigResolver imageConfigResolver;

    /**
     * Folder where to find project specific files
     */
    @Parameter(property = "fabric8.resourceDir", defaultValue = "${basedir}/src/main/fabric8")
    private File resourceDir;

    /**
     * Folder where to find project specific files
     */
    @Parameter(property = "fabric8.resourceDirOpenShiftOverride", defaultValue = "${basedir}/src/main/fabric8-openshift-override")
    private File resourceDirOpenShiftOverride;

    /**
     * Environment name where resources are placed. For example, if you set this property to dev and resourceDir is the default one, Fabric8 will look at src/main/fabric8/dev
     * Same applies for resourceDirOpenShiftOverride property.
     */
    @Parameter(property = "fabric8.environment")
    private String environment;

    /**
     * Should we use the project's compile-time classpath to scan for additional enrichers/generators?
     */
    @Parameter(property = "fabric8.useProjectClasspath", defaultValue = "false")
    private boolean useProjectClasspath = false;

    /**
     * The fabric8 working directory
     */
    @Parameter(property = "fabric8.workDir", defaultValue = "${project.build.directory}/fabric8")
    private File workDir;

    /**
     * The fabric8 working directory
     */
    @Parameter(property = "fabric8.workDirOpenShiftOverride", defaultValue = "${project.build.directory}/fabric8-openshift-override")
    private File workDirOpenShiftOverride;

    // Resource specific configuration for this plugin
    @Parameter
    private ResourceConfig resources;

    @Parameter(property = "fabric8.mode")
    private RuntimeMode runtimeMode = RuntimeMode.DEFAULT;

    // Skip resource descriptors validation
    @Parameter(property = "fabric8.skipResourceValidation", defaultValue = "false")
    private Boolean skipResourceValidation;

    // Determine if the plugin should stop when a validation error is encountered
    @Parameter(property = "fabric8.failOnValidationError", defaultValue = "false")
    private Boolean failOnValidationError;

    // Reusing image configuration from d-m-p
    @Parameter
    private List<ImageConfiguration> images;

    /**
     * OpenShift build mode when an OpenShift build is performed.
     * Can be either "s2i" for an s2i binary build mode or "docker" for a binary
     * docker mode.
     */
    @Parameter(property = "fabric8.build.strategy")
    private OpenShiftBuildStrategy buildStrategy = OpenShiftBuildStrategy.s2i;

    @Parameter(property = "fabric8.build.switchToDeployment", defaultValue = "false")
    private Boolean switchToDeployment;
    /**
     * Profile to use. A profile contains the enrichers and generators to
     * use as well as their configuration. Profiles are looked up
     * in the classpath and can be provided as yaml files.
     *

     * However, any given enricher and or generator configuration overrides
     * the information provided by a profile.
     */
    @Parameter(property = "fabric8.profile")
    private String profile;

    /**
     * The generated openshift YAML file
     */
    @Parameter(property = "fabric8.openshiftManifest", defaultValue = DEFAULT_OPENSHIFT_MANIFEST)
    private File openshiftManifest;

    /**
     * Enricher specific configuration configuration given through
     * to the various enrichers.
     */

    // Resource specific configuration for this plugin
    @Parameter(property = "fabric8.gitRemote")
    private String gitRemote;

    @Parameter
    private ProcessorConfig enricher;

    /**
     * Configuration passed to generators
     */
    @Parameter
    private ProcessorConfig generator;

    // Whether to use replica sets or replication controller. Could be configurable
    // but for now leave it hidden.
    private boolean useReplicaSet = true;

    // The image configuration after resolving and customization
    private List<ImageConfiguration> resolvedImages;

    // Mapping for kind filenames
    @Parameter
    private List<MappingConfig> mappings;

    // Services
    private HandlerHub handlerHub;

    /**
     * Namespace to use when accessing Kubernetes or OpenShift
     */
    @Parameter(property = "fabric8.namespace")
    private String namespace;

    @Parameter(property = "fabric8.skipHealthCheck", defaultValue = "false")
    private Boolean skipHealthCheck;

    /**
     * The OpenShift deploy timeout in seconds:
     * See this issue for background of why for end users on slow wifi on their laptops
     * DeploymentConfigs usually barf: https://github.com/openshift/origin/issues/10531
     *
     * Please follow also the discussion at
     *

     *
https://github.com/fabric8io/fabric8-maven-plugin/pull/944#discussion_r116962969

     *
https://github.com/fabric8io/fabric8-maven-plugin/pull/794

     *

     * and the references within it for the reason of this ridiculous long default timeout
     * (in short: Its because Docker image download times are added to the deployment time, making
     * the default of 10 minutes quite unusable if multiple images are included in the deployment).
     */
    @Parameter(property = "fabric8.openshift.deployTimeoutSeconds", defaultValue = "3600")
    private Long openshiftDeployTimeoutSeconds;

    /**
     * If set to true it would set the container image reference to "", this is done to handle weird
     * behavior of Openshift 3.7 in which subsequent rollouts lead to ImagePullErr
     *
     * Please see discussion at
     *

     *
https://github.com/openshift/origin/issues/18406

     *
https://github.com/fabric8io/fabric8-maven-plugin/issues/1130

     *

     */
    @Parameter(property = "fabric8.openshift.trimImageInContainerSpec", defaultValue = "false")
    private Boolean trimImageInContainerSpec;

    @Parameter(property = "fabric8.openshift.generateRoute", defaultValue = "true")
    private Boolean generateRoute;

    @Parameter(property = "fabric8.openshift.enableAutomaticTrigger", defaultValue = "true")
    private Boolean enableAutomaticTrigger;

    @Parameter(property = "fabric8.openshift.imageChangeTrigger", defaultValue = "true")
    private Boolean enableImageChangeTrigger;

    @Parameter(property = "docker.skip.resource", defaultValue = "false")
    protected boolean skipResource;

    /**
     * The artifact type for attaching the generated resource file to the project.
     * Can be either 'json' or 'yaml'
     */
    @Parameter(property = "fabric8.resourceType")
    private ResourceFileType resourceFileType = yaml;
    @Component
    private MavenProjectHelper projectHelper;

    // resourceDir when environment has been applied
    private File realResourceDir;

    // resourceDirOpenShiftOverride when environment has been applied
    private File realResourceDirOpenShiftOverride;

    /**
     * Returns the Template if the list contains a single Template only otherwise returns null
     */
    protected static Template getSingletonTemplate(KubernetesList resources) {
        // if the list contains a single Template lets unwrap it
        if (resources != null) {
            List<HasMetadata> items = resources.getItems();
            if (items != null && items.size() == 1) {
                HasMetadata singleEntity = items.get(0);
                if (singleEntity instanceof Template) {
                    return (Template) singleEntity;
                }
            }
        }
        return null;
    }

    public static File writeResourcesIndividualAndComposite(KubernetesList resources, File resourceFileBase,
        ResourceFileType resourceFileType, Logger log, Boolean generateRoute) throws MojoExecutionException {

        //Creating a new items list. This will be used to generate openshift.yml
        List<HasMetadata> newItemList = new ArrayList<>();

        if (!generateRoute) {

            //if flag is set false, this will remove the Route resource from resources list
            for (HasMetadata item : resources.getItems()) {
                if (item.getKind().equalsIgnoreCase("Route")) {
                    continue;
                }
                newItemList.add(item);
            }

            //update the resource with new list
            resources.setItems(newItemList);
        }

        // entity is object which will be sent to writeResource for openshift.yml
        // if generateRoute is false, this will be set to resources with new list
        // otherwise it will be set to resources with old list.
        Object entity = resources;

        // if the list contains a single Template lets unwrap it
        // in resources already new or old as per condition is set.
        // no need to worry about this for dropping Route.
        Template template = getSingletonTemplate(resources);
        if (template != null) {
            entity = template;
        }

        File file = writeResource(resourceFileBase, entity, resourceFileType);

        // write separate files, one for each resource item
        // resources passed to writeIndividualResources is also new one.
        writeIndividualResources(resources, resourceFileBase, resourceFileType, log, generateRoute);
        return file;
    }

    private static void writeIndividualResources(KubernetesList resources, File targetDir,
        ResourceFileType resourceFileType, Logger log, Boolean generateRoute) throws MojoExecutionException {
        for (HasMetadata item : resources.getItems()) {
            String name = KubernetesHelper.getName(item);
            if (StringUtils.isBlank(name)) {
                log.error("No name for generated item %s", item);
                continue;
            }
            String itemFile = KubernetesResourceUtil.getNameWithSuffix(name, item.getKind());

            // Here we are writing individual file for all the resources.
            // if generateRoute is false and resource is route, we should not generate it.

            if (!(item.getKind().equalsIgnoreCase("Route") && !generateRoute)) {
                File itemTarget = new File(targetDir, itemFile);
                writeResource(itemTarget, item, resourceFileType);
            }
        }
    }

    private static File writeResource(File resourceFileBase, Object entity, ResourceFileType resourceFileType)
        throws MojoExecutionException {
        try {
            return ResourceUtil.save(resourceFileBase, entity, resourceFileType);
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to write resource to " + resourceFileBase + ". " + e, e);
        }
    }

    public void executeInternal() throws MojoExecutionException, MojoFailureException {
        if (skipResource) {
            return;
        }

        realResourceDir = ResourceDirCreator.getFinalResourceDir(resourceDir, environment);
        realResourceDirOpenShiftOverride = ResourceDirCreator.getFinalResourceDir(resourceDirOpenShiftOverride, environment);
        updateKindFilenameMappings();
        try {
            lateInit();
            final File remoteResources = resolveRemoteResources();
            // Resolve the Docker image build configuration
            resolvedImages = getResolvedImages(images, log);
            if (!skip && (!isPomProject() || hasFabric8Dir())) {
                // Extract and generate resources which can be a mix of Kubernetes and OpenShift resources
                KubernetesList resources;
                for(PlatformMode platformMode : new PlatformMode[] { PlatformMode.kubernetes, PlatformMode.openshift }) {
                    ResourceClassifier resourceClassifier = platformMode == PlatformMode.kubernetes ? ResourceClassifier.KUBERNETES
                            : ResourceClassifier.OPENSHIFT;

                    resources = generateResources(platformMode, resolvedImages, remoteResources);
                    writeResources(resources, resourceClassifier, generateRoute);
                    File resourceDir = new File(this.targetDir, resourceClassifier.getValue());
                    validateIfRequired(resourceDir, resourceClassifier);
                }
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to generate fabric8 descriptor", e);
        }
    }

    private File resolveRemoteResources() {
        if (this.resources != null) {
            final List<String> remotes = this.resources.getRemotes();
            if (remotes != null && !remotes.isEmpty()) {
                final File tempDirectory = FileUtil.createTempDirectory();
                FileUtil.downloadRemotes(tempDirectory, remotes, this.log);
                return tempDirectory;
            }
        }
        return null;
    }

    private void updateKindFilenameMappings() {
        if (mappings != null) {
            final Map<String, List<String>> mappingKindFilename = new HashMap<>();
            for (MappingConfig mappingConfig : this.mappings) {
                if (mappingConfig.isValid()) {
                    mappingKindFilename.put(mappingConfig.getKind(), Arrays.asList(mappingConfig.getFilenamesAsArray()));
                } else {
                    throw new IllegalArgumentException(String.format("Invalid mapping for Kind %s and Filename Types %s",
                        mappingConfig.getKind(), mappingConfig.getFilenameTypes()));
                }
            }
            KubernetesResourceUtil.updateKindFilenameMapper(mappingKindFilename);
        }
    }

    private void validateIfRequired(File resourceDir, ResourceClassifier classifier)
        throws MojoExecutionException, MojoFailureException {
        try {
            if (!skipResourceValidation) {
                new ResourceValidator(resourceDir, classifier, log).validate();
            }
        } catch (ConstraintViolationException e) {
            if (failOnValidationError) {
                log.error("[[R]]" + e.getMessage() + "[[R]]");
                log.error("[[R]]use \"mvn -Dfabric8.skipResourceValidation=true\" option to skip the validation[[R]]");
                throw new MojoFailureException("Failed to generate fabric8 descriptor");
            } else {
                log.warn("[[Y]]" + e.getMessage() + "[[Y]]");
            }
        } catch (Throwable e) {
            if (failOnValidationError) {
                throw new MojoExecutionException("Failed to validate resources", e);
            } else {
                log.warn("Failed to validate resources: %s", e.getMessage());
            }
        }
    }

    private void lateInit() {
        ClusterAccess clusterAccess = new ClusterAccess(getClusterConfiguration());
        runtimeMode = new ClusterAccess(getClusterConfiguration()).resolveRuntimeMode(runtimeMode, log);
        if (runtimeMode.equals(RuntimeMode.openshift)) {
            Properties properties = project.getProperties();
            if (!properties.contains(DOCKER_IMAGE_USER)) {
                String namespace = this.namespace != null && !this.namespace.isEmpty() ?
                        this.namespace: clusterAccess.getNamespace();
                log.info("Using docker image name of namespace: " + namespace);
                properties.setProperty(DOCKER_IMAGE_USER, namespace);
            }
            if (!properties.contains(RuntimeMode.FABRIC8_EFFECTIVE_PLATFORM_MODE)) {
                properties.setProperty(RuntimeMode.FABRIC8_EFFECTIVE_PLATFORM_MODE, runtimeMode.toString());
            }
        }
    }

    private KubernetesList generateResources(PlatformMode platformMode, List<ImageConfiguration> images, File remoteResources)
        throws IOException, MojoExecutionException {

        if (namespace != null && !namespace.isEmpty()) {
            resources = new ResourceConfig.Builder(resources).withNamespace(namespace).build();
        }
        // Manager for calling enrichers.
        MavenEnricherContext.Builder ctxBuilder = new MavenEnricherContext.Builder()
                .project(project)
                .session(session)
                .config(extractEnricherConfig())
                .settings(settings)
                .properties(project.getProperties())
                .resources(resources)
                .images(resolvedImages)
                .log(log);

        EnricherManager enricherManager = new EnricherManager(resources, ctxBuilder.build(),
            MavenUtil.getCompileClasspathElementsIfRequested(project, useProjectClasspath));

        // Generate all resources from the main resource directory, configuration and create them accordingly
        KubernetesListBuilder builder = generateAppResources(platformMode, images, enricherManager, remoteResources);

        // Add resources found in subdirectories of resourceDir, with a certain profile
        // applied
        addProfiledResourcesFromSubirectories(platformMode, builder, realResourceDir, enricherManager);
        return builder.build();
    }

    private void addProfiledResourcesFromSubirectories(PlatformMode platformMode, KubernetesListBuilder builder, File resourceDir,
        EnricherManager enricherManager) throws IOException, MojoExecutionException {
        File[] profileDirs = resourceDir.listFiles((File pathname) -> pathname.isDirectory());
        if (profileDirs != null) {
            for (File profileDir : profileDirs) {
                Profile profile = ProfileUtil.findProfile(profileDir.getName(), resourceDir);
                if (profile == null) {
                    throw new MojoExecutionException(String.format("Invalid profile '%s' given as directory in %s. " +
                            "Please either define a profile of this name or move this directory away",
                        profileDir.getName(), resourceDir));
                }

                ProcessorConfig enricherConfig = profile.getEnricherConfig();
                File[] resourceFiles = KubernetesResourceUtil.listResourceFragments(profileDir);
                if (resourceFiles.length > 0) {
                    KubernetesListBuilder profileBuilder = readResourceFragments(platformMode, resourceFiles);
                    enricherManager.createDefaultResources(platformMode, enricherConfig, profileBuilder);
                    enricherManager.enrich(platformMode, enricherConfig, profileBuilder);
                    KubernetesList profileItems = profileBuilder.build();
                    for (HasMetadata item : profileItems.getItems()) {
                        builder.addToItems(item);
                    }
                }
            }
        }
    }

    private KubernetesListBuilder generateAppResources(PlatformMode platformMode, List<ImageConfiguration> images, EnricherManager enricherManager,
        File remoteResources)
        throws IOException, MojoExecutionException {
        try {
            KubernetesListBuilder builder = processResourceFragments(platformMode, remoteResources);

            // Create default resources for app resources only
            enricherManager.createDefaultResources(platformMode, builder);

            // Enrich descriptors
            enricherManager.enrich(platformMode, builder);

            return builder;
        } catch (ConstraintViolationException e) {
            String message = ValidationUtil.createValidationMessage(e.getConstraintViolations());
            log.error("ConstraintViolationException: %s", message);
            throw new MojoExecutionException(message, e);
        }
    }

    private KubernetesListBuilder processResourceFragments(PlatformMode platformMode, File remoteResources) throws IOException, MojoExecutionException {
        File[] resourceFiles = KubernetesResourceUtil.listResourceFragments(realResourceDir);
        if (remoteResources != null && remoteResources.isDirectory()) {
            final File[] remoteFragments = remoteResources.listFiles();
            resourceFiles = ArrayUtils.addAll(resourceFiles, remoteFragments);
        }
        KubernetesListBuilder builder;

        // Add resource files found in the fabric8 directory
        if (resourceFiles != null && resourceFiles.length > 0) {
            log.info("using resource templates from %s", realResourceDir);
            builder = readResourceFragments(platformMode, resourceFiles);
        } else {
            builder = new KubernetesListBuilder();
        }
        return builder;
    }

    private KubernetesListBuilder readResourceFragments(PlatformMode platformMode, File[] resourceFiles) throws IOException, MojoExecutionException {
        KubernetesListBuilder builder;
        String defaultName = MavenUtil.createDefaultResourceName(project.getArtifactId());
        builder = KubernetesResourceUtil.readResourceFragmentsFrom(
            platformMode,
            KubernetesResourceUtil.DEFAULT_RESOURCE_VERSIONING,
            defaultName,
            mavenFilterFiles(resourceFiles, this.workDir));
        return builder;
    }

    private ProcessorConfig extractEnricherConfig() throws IOException {
        return ProfileUtil.blendProfileWithConfiguration(ProfileUtil.ENRICHER_CONFIG, profile, realResourceDir, enricher);
    }

    private ProcessorConfig extractGeneratorConfig() throws IOException {
        return ProfileUtil.blendProfileWithConfiguration(ProfileUtil.GENERATOR_CONFIG, profile, realResourceDir, generator);
    }

    // ==================================================================================

    private List<ImageConfiguration> getResolvedImages(List<ImageConfiguration> images, final Logger log)
        throws MojoExecutionException {
        List<ImageConfiguration> ret;
        ret = ConfigHelper.resolveImages(
            log,
            images,
            (ImageConfiguration image) -> imageConfigResolver.resolve(image, project, session),
            null,  // no filter on image name yet (TODO: Maybe add this, too ?)
                (List<ImageConfiguration> configs) -> {
                    try {
                        GeneratorContext ctx = new GeneratorContext.Builder()
                                .config(extractGeneratorConfig())
                                .project(project)
                                .runtimeMode(runtimeMode)
                                .logger(log)
                                .strategy(buildStrategy)
                                .useProjectClasspath(useProjectClasspath)
                                .build();
                        return GeneratorManager.generate(configs, ctx, true);
                    } catch (Exception e) {
                        throw new IllegalArgumentException("Cannot extract generator: " + e, e);
                    }
            });

        Date now = getBuildReferenceDate();
        storeReferenceDateInPluginContext(now);
        String minimalApiVersion = ConfigHelper.initAndValidate(ret, null /* no minimal api version */,
            new ImageNameFormatter(project, now), log);
        return ret;
    }

    private void storeReferenceDateInPluginContext(Date now) {
        Map<String, Object> pluginContext = getPluginContext();
        pluginContext.put(AbstractDockerMojo.CONTEXT_KEY_BUILD_TIMESTAMP, now);
    }

    // get a reference date
    private Date getBuildReferenceDate() throws MojoExecutionException {
        // Pick up an existing build date created by fabric8:build previously
        File tsFile = new File(project.getBuild().getDirectory(), AbstractDockerMojo.DOCKER_BUILD_TIMESTAMP);
        if (!tsFile.exists()) {
            return new Date();
        }
        try {
            return EnvUtil.loadTimestamp(tsFile);
        } catch (IOException e) {
            throw new MojoExecutionException("Cannot read timestamp from " + tsFile, e);
        }
    }

    private File[] mavenFilterFiles(File[] resourceFiles, File outDir) throws MojoExecutionException {
        if (!outDir.exists()) {
            if (!outDir.mkdirs()) {
                throw new MojoExecutionException("Cannot create working dir " + outDir);
            }
        }
        File[] ret = new File[resourceFiles.length];
        int i = 0;
        for (File resource : resourceFiles) {
            File targetFile = new File(outDir, resource.getName());
            try {
                mavenFileFilter.copyFile(resource, targetFile, true,
                    project, null, false, "utf8", session);
                ret[i++] = targetFile;
            } catch (MavenFilteringException exp) {
                throw new MojoExecutionException(
                    String.format("Cannot filter %s to %s", resource, targetFile), exp);
            }
        }
        return ret;
    }

    private boolean hasFabric8Dir() {
        return realResourceDir.isDirectory();
    }

    private boolean isPomProject() {
        return "pom".equals(project.getPackaging());
    }

    protected void writeResources(KubernetesList resources, ResourceClassifier classifier, Boolean generateRoute)
        throws MojoExecutionException {
        // write kubernetes.yml / openshift.yml
        File resourceFileBase = new File(this.targetDir, classifier.getValue());

        File file =
            writeResourcesIndividualAndComposite(resources, resourceFileBase, this.resourceFileType, log, generateRoute);

        // Attach it to the Maven reactor so that it will also get deployed
        projectHelper.attachArtifact(project, this.resourceFileType.getArtifactType(), classifier.getValue(), file);
    }

    protected ClusterConfiguration getClusterConfiguration() {
        final ClusterConfiguration.Builder clusterConfigurationBuilder = new ClusterConfiguration.Builder(access);

        return clusterConfigurationBuilder.from(System.getProperties())
                .from(project.getProperties()).build();
    }
}