View Javadoc
1   /*
2    * Copyright 2017-2022 original authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package io.micronaut.build;
17  
18  import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
19  import com.fasterxml.jackson.databind.ObjectMapper;
20  import com.fasterxml.jackson.databind.ObjectWriter;
21  import org.apache.maven.model.Resource;
22  import org.apache.maven.plugin.MojoExecutionException;
23  import org.apache.maven.plugins.annotations.Mojo;
24  import org.apache.maven.plugins.annotations.Parameter;
25  import org.apache.maven.plugins.resources.ResourcesMojo;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import java.util.*;
33  import java.util.stream.Collectors;
34  
35  /**
36   * <p>Generate GraalVM <code>resources-config.json</code> with all the resources included in the application</p>
37   * <p><strong>WARNING</strong>: this goal is not intended to be executed directly.</p>
38   *
39   * @author Iván López
40   * @since 2.0
41   */
42  @Mojo(name = GraalVMResourcesMojo.GRAALVM_RESOURCES)
43  public class GraalVMResourcesMojo extends ResourcesMojo {
44  
45      public static final String GRAALVM_RESOURCES = "graalvm-resources";
46  
47      private static final String META_INF = "META-INF";
48      private static final String RESOURCES = "resources";
49      private static final String PATTERN = "pattern";
50      private static final String RESOURCE_CONFIG_JSON = "resource-config.json";
51      private static final List<String> EXCLUDED_META_INF_DIRECTORIES = Arrays.asList("native-image", "services");
52  
53      private final ObjectWriter writer = new ObjectMapper().writer(new DefaultPrettyPrinter());
54  
55      @Parameter(property = "micronaut.native-image.skip-resources", defaultValue = "false")
56      private Boolean nativeImageSkipResources;
57  
58      @Override
59      public void execute() throws MojoExecutionException {
60          if (Boolean.TRUE.equals(nativeImageSkipResources)) {
61              getLog().info("Skipping generation of resource-config.json");
62              return;
63          }
64  
65          Set<String> resourcesToAdd = new HashSet<>();
66  
67          // Application resources (src/main/resources)
68          for (Resource resource : getResources()) {
69              resourcesToAdd.addAll(findResourceFiles(Paths.get(resource.getDirectory()).toFile()));
70          }
71  
72          // Generated resources (like openapi)
73          Path metaInfPath = getOutputDirectory().toPath().resolve(META_INF);
74          resourcesToAdd.addAll(findResourceFiles(metaInfPath.toFile(), Collections.singletonList(META_INF)));
75  
76          Path nativeImagePath = buildNativeImagePath();
77          Path graalVMResourcesPath = metaInfPath.resolve(nativeImagePath).toAbsolutePath();
78  
79          Map<String, Object> json = new HashMap<>();
80          List<Map<String, String>> resourceList = resourcesToAdd.stream()
81                  .map(this::mapToGraalResource)
82                  .collect(Collectors.toList());
83  
84          json.put(RESOURCES, resourceList);
85  
86          try {
87              Files.createDirectories(graalVMResourcesPath);
88              File resourceConfigFile = graalVMResourcesPath.resolve(RESOURCE_CONFIG_JSON).toFile();
89              getLog().info("Generating " + resourceConfigFile.getAbsolutePath());
90              writer.writeValue(resourceConfigFile, json);
91  
92          } catch (IOException e) {
93              throw new MojoExecutionException("There was an error generating GraalVM resource-config.json file", e);
94          }
95      }
96  
97      private Set<String> findResourceFiles(File folder) {
98          return this.findResourceFiles(folder, null);
99      }
100 
101     private Set<String> findResourceFiles(File folder, List<String> filePath) {
102         Set<String> resourceFiles = new HashSet<>();
103 
104         if (filePath == null) {
105             filePath = new ArrayList<>();
106         }
107 
108         if (folder.exists()) {
109             File[] files = folder.listFiles();
110 
111             if (files != null) {
112                 boolean isMetaInfDirectory = folder.getName().equals(META_INF);
113 
114                 for (File element : files) {
115                     boolean isExcludedDirectory = EXCLUDED_META_INF_DIRECTORIES.contains(element.getName());
116                     // Exclude some directories in 'META-INF' like 'native-image' and 'services' but process other
117                     // 'META-INF' files and directories, for example, to include swagger-ui.
118                     if (!isMetaInfDirectory || !isExcludedDirectory) {
119                         if (element.isDirectory()) {
120                             List<String> paths = new ArrayList<>(filePath);
121                             paths.add(element.getName());
122 
123                             resourceFiles.addAll(findResourceFiles(element, paths));
124                         } else {
125                             String joinedDirectories = String.join("/", filePath);
126                             String elementName = joinedDirectories.isEmpty() ? element.getName() : joinedDirectories + "/" + element.getName();
127 
128                             resourceFiles.add(elementName);
129                         }
130                     }
131                 }
132             }
133         }
134 
135         return resourceFiles;
136     }
137 
138     private Path buildNativeImagePath() {
139         String group = project.getGroupId();
140         String module = project.getArtifactId();
141 
142         return Paths.get("native-image", group, module);
143     }
144 
145     private Map<String, String> mapToGraalResource(String resourceName) {
146         Map<String, String> result = new HashMap<>();
147 
148         if (resourceName.contains("*")) {
149             result.put(PATTERN, resourceName);
150         } else {
151             result.put(PATTERN, "\\Q" + resourceName + "\\E");
152         }
153 
154         return result;
155     }
156 }