001/*
002 * Copyright 2017-2022 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven;
017
018import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
019import com.fasterxml.jackson.databind.ObjectMapper;
020import com.fasterxml.jackson.databind.ObjectWriter;
021import org.apache.maven.model.Resource;
022import org.apache.maven.plugin.MojoExecutionException;
023import org.apache.maven.plugin.logging.Log;
024import org.apache.maven.plugins.annotations.Mojo;
025import org.apache.maven.plugins.annotations.Parameter;
026import org.apache.maven.plugins.resources.ResourcesMojo;
027
028import java.io.File;
029import java.io.IOException;
030import java.nio.file.Files;
031import java.nio.file.Path;
032import java.nio.file.Paths;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Collections;
036import java.util.HashMap;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041
042/**
043 * <p>Generate GraalVM <code>resources-config.json</code> with all the resources included in the application</p>
044 * <p><strong>WARNING</strong>: this goal is not intended to be executed directly.</p>
045 *
046 * @author Iván López
047 * @since 2.0
048 * @deprecated Please use native:generateResourceConfig and/or native:generateTestResourceConfig instead.
049 */
050@Mojo(name = GraalVMResourcesMojo.GRAALVM_RESOURCES)
051@Deprecated(since = "4.4.0", forRemoval = true)
052public class GraalVMResourcesMojo extends ResourcesMojo {
053
054    public static final String GRAALVM_RESOURCES = "graalvm-resources";
055
056    private static final String META_INF = "META-INF";
057    private static final String RESOURCES = "resources";
058    private static final String PATTERN = "pattern";
059    private static final String RESOURCE_CONFIG_JSON = "resource-config.json";
060    private static final List<String> EXCLUDED_META_INF_DIRECTORIES = Arrays.asList("native-image", "services");
061
062    private final ObjectWriter writer = new ObjectMapper().writer(new DefaultPrettyPrinter());
063
064    @Parameter(property = "micronaut.native-image.skip-resources", defaultValue = "false")
065    private Boolean nativeImageSkipResources;
066
067    @Override
068    public void execute() throws MojoExecutionException {
069        if (Boolean.TRUE.equals(nativeImageSkipResources)) {
070            getLog().info("Skipping generation of resource-config.json");
071            return;
072        }
073
074        var resourcesToAdd = new HashSet<String>();
075
076        // Application resources (src/main/resources)
077        for (Resource resource : getResources()) {
078            resourcesToAdd.addAll(findResourceFiles(Paths.get(resource.getDirectory()).toFile()));
079        }
080
081        // Generated resources (like openapi)
082        Path metaInfPath = getOutputDirectory().toPath().resolve(META_INF);
083        resourcesToAdd.addAll(findResourceFiles(metaInfPath.toFile(), Collections.singletonList(META_INF)));
084
085        Path nativeImagePath = buildNativeImagePath();
086        Path graalVMResourcesPath = metaInfPath.resolve(nativeImagePath).toAbsolutePath();
087
088        var json = new HashMap<String, Object>();
089        List<Map<String, String>> resourceList = resourcesToAdd.stream()
090            .map(this::mapToGraalResource)
091            .toList();
092
093        json.put(RESOURCES, resourceList);
094
095        try {
096            Files.createDirectories(graalVMResourcesPath);
097            File resourceConfigFile = graalVMResourcesPath.resolve(RESOURCE_CONFIG_JSON).toFile();
098            getLog().info("Generating " + resourceConfigFile.getAbsolutePath());
099            writer.writeValue(resourceConfigFile, json);
100
101        } catch (IOException e) {
102            throw new MojoExecutionException("There was an error generating GraalVM resource-config.json file", e);
103        }
104    }
105
106    private Set<String> findResourceFiles(File folder) {
107        return this.findResourceFiles(folder, null);
108    }
109
110    private Set<String> findResourceFiles(File folder, List<String> filePath) {
111        var resourceFiles = new HashSet<String>();
112
113        if (filePath == null) {
114            filePath = new ArrayList<>();
115        }
116
117        if (folder.exists()) {
118            File[] files = folder.listFiles();
119
120            if (files != null) {
121                boolean isMetaInfDirectory = folder.getName().equals(META_INF);
122
123                for (File element : files) {
124                    boolean isExcludedDirectory = EXCLUDED_META_INF_DIRECTORIES.contains(element.getName());
125                    // Exclude some directories in 'META-INF' like 'native-image' and 'services' but process other
126                    // 'META-INF' files and directories, for example, to include swagger-ui.
127                    if (!isMetaInfDirectory || !isExcludedDirectory) {
128                        if (element.isDirectory()) {
129                            var paths = new ArrayList<>(filePath);
130                            paths.add(element.getName());
131
132                            resourceFiles.addAll(findResourceFiles(element, paths));
133                        } else {
134                            String joinedDirectories = String.join("/", filePath);
135                            String elementName = joinedDirectories.isEmpty() ? element.getName() : joinedDirectories + "/" + element.getName();
136
137                            resourceFiles.add(elementName);
138                        }
139                    }
140                }
141            }
142        }
143
144        return resourceFiles;
145    }
146
147    private Path buildNativeImagePath() {
148        String group = project.getGroupId();
149        String module = project.getArtifactId();
150
151        return Paths.get("native-image", group, module);
152    }
153
154    private Map<String, String> mapToGraalResource(String resourceName) {
155        var result = new HashMap<String, String>();
156
157        if (resourceName.contains("*")) {
158            result.put(PATTERN, resourceName);
159        } else {
160            result.put(PATTERN, "\\Q" + resourceName + "\\E");
161        }
162
163        return result;
164    }
165
166    @Override
167    public void setLog(Log log) {
168        super.setLog(new JansiLog(log));
169    }
170}