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