1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.devops.conga.tooling.maven.plugin;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.Properties;
29 import java.util.SortedSet;
30 import java.util.function.Predicate;
31 import java.util.stream.Collectors;
32
33 import javax.inject.Inject;
34
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
38 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
39 import org.apache.maven.execution.MavenSession;
40 import org.apache.maven.plugin.MojoExecutionException;
41 import org.apache.maven.plugin.MojoFailureException;
42 import org.apache.maven.plugins.annotations.LifecyclePhase;
43 import org.apache.maven.plugins.annotations.Mojo;
44 import org.apache.maven.plugins.annotations.Parameter;
45 import org.apache.maven.plugins.annotations.ResolutionScope;
46 import org.apache.maven.project.MavenProject;
47 import org.eclipse.aether.RepositorySystem;
48 import org.eclipse.aether.RepositorySystemSession;
49 import org.eclipse.aether.repository.RemoteRepository;
50 import org.sonatype.plexus.build.incremental.BuildContext;
51 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
52
53 import io.wcm.devops.conga.generator.GeneratorException;
54 import io.wcm.devops.conga.generator.GeneratorOptions;
55 import io.wcm.devops.conga.generator.UrlFileManager;
56 import io.wcm.devops.conga.generator.handlebars.HandlebarsManager;
57 import io.wcm.devops.conga.generator.spi.context.PluginContextOptions;
58 import io.wcm.devops.conga.generator.spi.context.UrlFilePluginContext;
59 import io.wcm.devops.conga.generator.util.PluginManager;
60 import io.wcm.devops.conga.generator.util.PluginManagerImpl;
61 import io.wcm.devops.conga.model.environment.Environment;
62 import io.wcm.devops.conga.model.reader.EnvironmentReader;
63 import io.wcm.devops.conga.model.reader.RoleReader;
64 import io.wcm.devops.conga.model.role.Role;
65 import io.wcm.devops.conga.resource.Resource;
66 import io.wcm.devops.conga.resource.ResourceCollection;
67 import io.wcm.devops.conga.resource.ResourceInfo;
68 import io.wcm.devops.conga.resource.ResourceLoader;
69 import io.wcm.devops.conga.tooling.maven.plugin.util.ClassLoaderUtil;
70 import io.wcm.devops.conga.tooling.maven.plugin.util.MavenContext;
71 import io.wcm.devops.conga.tooling.maven.plugin.util.PathUtil;
72 import io.wcm.devops.conga.tooling.maven.plugin.util.VersionInfoUtil;
73 import io.wcm.devops.conga.tooling.maven.plugin.validation.DefinitionValidator;
74 import io.wcm.devops.conga.tooling.maven.plugin.validation.ModelValidator;
75 import io.wcm.devops.conga.tooling.maven.plugin.validation.NoValueProviderInRoleValidator;
76 import io.wcm.devops.conga.tooling.maven.plugin.validation.RoleTemplateFileValidator;
77 import io.wcm.devops.conga.tooling.maven.plugin.validation.TemplateValidator;
78
79
80
81
82
83
84 @Mojo(name = "validate", defaultPhase = LifecyclePhase.VALIDATE, requiresProject = true, threadSafe = true,
85 requiresDependencyResolution = ResolutionScope.COMPILE)
86 public class ValidateMojo extends AbstractCongaMojo {
87
88
89
90
91 @Parameter(property = "conga.environments")
92 private String[] environments;
93
94 @Parameter(property = "project", required = true, readonly = true)
95 private MavenProject project;
96
97 @Inject
98 private org.apache.maven.repository.RepositorySystem repositorySystem;
99 @Inject
100 private ResolutionErrorHandler resolutionErrorHandler;
101 @Inject
102 private RepositorySystem repoSystem;
103 @Inject
104 private BuildContext buildContext;
105 @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
106 private RepositorySystemSession repoSession;
107 @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true)
108 private List<RemoteRepository> remoteRepos;
109 @Parameter(defaultValue = "${session}", readonly = true, required = false)
110 private MavenSession session;
111
112 @Override
113 public void execute() throws MojoExecutionException, MojoFailureException {
114 List<URL> mavenProjectClasspathUrls = ClassLoaderUtil.getMavenProjectClasspathUrls(project);
115 ClassLoader mavenProjectClassLoader = ClassLoaderUtil.buildClassLoader(mavenProjectClasspathUrls);
116 ResourceLoader mavenProjectResourceLoader = new ResourceLoader(mavenProjectClassLoader);
117
118 ResourceCollection roleDir = mavenProjectResourceLoader.getResourceCollection(ResourceLoader.FILE_PREFIX + getRoleDir());
119 ResourceCollection templateDir = mavenProjectResourceLoader.getResourceCollection(ResourceLoader.FILE_PREFIX + getTemplateDir());
120 ResourceCollection environmentDir = mavenProjectResourceLoader.getResourceCollection(ResourceLoader.FILE_PREFIX + getEnvironmentDir());
121
122
123 validateFiles(roleDir, roleDir, new ModelValidator<Role>("Role", new RoleReader()));
124
125 PluginManager pluginManager = new PluginManagerImpl();
126
127 MavenContext mavenContext = new MavenContext()
128 .project(project)
129 .session(session)
130 .setRepositorySystem(repositorySystem)
131 .resolutionErrorHandler(resolutionErrorHandler)
132 .buildContext(buildContext)
133 .log(getLog())
134 .repoSystem(repoSystem)
135 .repoSession(repoSession)
136 .remoteRepos(remoteRepos)
137 .artifactTypeMappings(getArtifactTypeMappings());
138
139 UrlFilePluginContext urlFilePluginContext = new UrlFilePluginContext()
140 .baseDir(project.getBasedir())
141 .resourceClassLoader(mavenProjectClassLoader)
142 .pluginContextOptions(new PluginContextOptions()
143 .containerContext(mavenContext));
144 UrlFileManager urlFileManager = new UrlFileManager(pluginManager, urlFilePluginContext);
145
146 PluginContextOptions pluginContextOptions = new PluginContextOptions()
147 .pluginManager(pluginManager)
148 .urlFileManager(urlFileManager)
149 .valueProviderConfig(getValueProviderConfig())
150 .genericPluginConfig(getPluginConfig())
151 .containerContext(mavenContext)
152 .logger(new MavenSlf4jLogFacade(getLog()));
153
154
155 HandlebarsManager handlebarsManager = new HandlebarsManager(List.of(templateDir), pluginContextOptions);
156 validateFiles(templateDir, templateDir, new TemplateValidator(templateDir, handlebarsManager));
157
158
159 validateFiles(roleDir, roleDir, new RoleTemplateFileValidator(handlebarsManager));
160
161
162 validateFiles(roleDir, roleDir, new NoValueProviderInRoleValidator());
163
164
165 List<Environment> environmentList = validateFiles(environmentDir, environmentDir, new ModelValidator<Environment>("Environment", new EnvironmentReader()),
166
167 resourceInfo -> {
168 if (this.environments == null || this.environments.length == 0) {
169 return true;
170 }
171 for (String environment : this.environments) {
172 if (StringUtils.equals(environment, FilenameUtils.getBaseName(resourceInfo.getName()))) {
173 return true;
174 }
175 }
176 return false;
177 });
178
179
180 for (Environment environment : environmentList) {
181 UrlFilePluginContext environmentUrlFilePluginContext = new UrlFilePluginContext()
182 .baseDir(project.getBasedir())
183 .resourceClassLoader(mavenProjectClassLoader)
184 .environment(environment)
185 .pluginContextOptions(new PluginContextOptions()
186 .containerContext(mavenContext));
187 UrlFileManager environmentUrlFileManager = new UrlFileManager(pluginManager, environmentUrlFilePluginContext);
188
189 PluginContextOptions environmentPluginContextOptions = new PluginContextOptions()
190 .pluginContextOptions(pluginContextOptions)
191 .urlFileManager(environmentUrlFileManager);
192 validateVersionInfo(environment, mavenProjectClasspathUrls, environmentPluginContextOptions);
193 }
194 }
195
196
197
198 private <T> List<T> validateFiles(ResourceCollection sourceDir, ResourceCollection rootSourceDir, DefinitionValidator<T> validator)
199 throws MojoFailureException {
200 return validateFiles(sourceDir, rootSourceDir, validator, resourceInfo -> true);
201 }
202
203 private <T> List<T> validateFiles(ResourceCollection sourceDir, ResourceCollection rootSourceDir, DefinitionValidator<T> validator,
204 Predicate<ResourceInfo> resourceFilter) throws MojoFailureException {
205 if (!sourceDir.exists()) {
206 return List.of();
207 }
208 SortedSet<Resource> files = sourceDir.getResources();
209 SortedSet<ResourceCollection> dirs = sourceDir.getResourceCollections();
210 if (files.isEmpty() && dirs.isEmpty()) {
211 return List.of();
212 }
213
214 List<T> result = new ArrayList<>();
215 for (Resource file : files) {
216 if (resourceFilter.test(file)) {
217 result.add(validator.validate(file, getPathForLog(rootSourceDir, file)));
218 }
219 }
220 for (ResourceCollection dir : dirs) {
221 if (resourceFilter.test(dir)) {
222 result.addAll(validateFiles(dir, rootSourceDir, validator, resourceFilter));
223 }
224 }
225 return result;
226 }
227
228 @SuppressWarnings("java:S1075")
229 private static String getPathForLog(ResourceCollection rootSourceDir, Resource file) {
230 String path = PathUtil.unifySlashes(file.getCanonicalPath());
231 String rootPath = PathUtil.unifySlashes(rootSourceDir.getCanonicalPath()) + "/";
232 return StringUtils.substringAfter(path, rootPath);
233 }
234
235
236
237
238
239
240
241
242
243
244 private void validateVersionInfo(Environment environment, List<URL> mavenProjectClasspathUrls, PluginContextOptions pluginContextOptions)
245 throws MojoExecutionException {
246
247
248 List<URL> classpathUrls = new ArrayList<>();
249 classpathUrls.addAll(getEnvironmentClasspathUrls(environment.getDependencies(), pluginContextOptions));
250 classpathUrls.addAll(mavenProjectClasspathUrls);
251 ClassLoader environmentDependenciesClassLoader = ClassLoaderUtil.buildClassLoader(classpathUrls);
252
253
254 Properties currentVersionInfo = VersionInfoUtil.getVersionInfoProperties(project);
255
256
257 for (Properties dependencyVersionInfo : getDependencyVersionInfos(environmentDependenciesClassLoader)) {
258 validateVersionInfo(currentVersionInfo, dependencyVersionInfo);
259 }
260
261 }
262
263 private List<URL> getEnvironmentClasspathUrls(List<String> dependencyUrls, PluginContextOptions pluginContextOptions) {
264 return dependencyUrls.stream()
265 .map(dependencyUrl -> {
266 String resolvedDependencyUrl = ClassLoaderUtil.resolveDependencyUrl(dependencyUrl, pluginContextOptions);
267 try {
268 return pluginContextOptions.getUrlFileManager().getFileUrlsWithDependencies(resolvedDependencyUrl);
269 }
270 catch (IOException ex) {
271 throw new GeneratorException("Unable to resolve: " + resolvedDependencyUrl, ex);
272 }
273 })
274 .flatMap(List::stream)
275 .collect(Collectors.toList());
276 }
277
278 private void validateVersionInfo(Properties currentVersionInfo, Properties dependencyVersionInfo) throws MojoExecutionException {
279 for (Object keyObject : currentVersionInfo.keySet()) {
280 String key = keyObject.toString();
281 String currentVersionString = currentVersionInfo.getProperty(key);
282 String dependencyVersionString = dependencyVersionInfo.getProperty(key);
283 if (StringUtils.isEmpty(currentVersionString) || StringUtils.isEmpty(dependencyVersionString)) {
284 continue;
285 }
286 DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(currentVersionString);
287 DefaultArtifactVersion dependencyVersion = new DefaultArtifactVersion(dependencyVersionString);
288 if (currentVersion.compareTo(dependencyVersion) < 0) {
289 throw new MojoExecutionException("Newer CONGA maven plugin or plugin version required: " + key + ":" + dependencyVersion.toString());
290 }
291 }
292 }
293
294 private List<Properties> getDependencyVersionInfos(ClassLoader classLoader) throws MojoExecutionException {
295 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);
296 try {
297 org.springframework.core.io.Resource[] resources = resolver.getResources(
298 "classpath*:" + GeneratorOptions.CLASSPATH_PREFIX + BuildConstants.FILE_VERSION_INFO);
299 return Arrays.stream(resources)
300 .map(this::toProperties)
301 .collect(Collectors.toList());
302 }
303 catch (IOException ex) {
304 throw new MojoExecutionException("Unable to get classpath resources: " + ex.getMessage(), ex);
305 }
306 }
307
308 private Properties toProperties(org.springframework.core.io.Resource resource) {
309 try (InputStream is = resource.getInputStream()) {
310 Properties props = new Properties();
311 props.load(is);
312 return props;
313 }
314 catch (IOException ex) {
315 throw new IllegalArgumentException("Unable to read properties file: " + resource.toString(), ex);
316 }
317 }
318
319 }