• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ethlo / zally-maven-plugin / 47

pending completion
47

Pull #9

travis-ci-com

web-flow
Merge d242a4eb1 into 8c4830a27
Pull Request #9: Upgrade to latest Zally libraries.

171 of 531 relevant lines covered (32.2%)

0.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

15.22
/src/main/java/com/ethlo/zally/ZallyMojo.java
1
package com.ethlo.zally;/*-
2
 * #%L
3
 * zally-maven-plugin
4
 * %%
5
 * Copyright (C) 2021 Morten Haraldsen (ethlo)
6
 * %%
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *      http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 * #L%
19
 */
20

21
import java.io.IOException;
22
import java.io.UncheckedIOException;
23
import java.nio.file.Files;
24
import java.nio.file.Path;
25
import java.nio.file.Paths;
26
import java.nio.file.StandardOpenOption;
27
import java.util.ArrayList;
28
import java.util.Arrays;
29
import java.util.Comparator;
30
import java.util.LinkedHashMap;
31
import java.util.LinkedHashSet;
32
import java.util.LinkedList;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.Optional;
36
import java.util.Set;
37
import java.util.TreeMap;
38
import java.util.stream.Collectors;
39

40
import org.apache.commons.lang3.StringUtils;
41
import org.apache.maven.plugin.AbstractMojo;
42
import org.apache.maven.plugin.MojoFailureException;
43
import org.apache.maven.plugins.annotations.LifecyclePhase;
44
import org.apache.maven.plugins.annotations.Mojo;
45
import org.apache.maven.plugins.annotations.Parameter;
46
import org.apache.maven.project.MavenProject;
47
import org.zalando.zally.core.CheckDetails;
48
import org.zalando.zally.core.Result;
49
import org.zalando.zally.core.RuleDetails;
50
import org.zalando.zally.rule.api.Severity;
51

52
import com.fasterxml.jackson.annotation.JsonInclude;
53
import com.fasterxml.jackson.core.JsonProcessingException;
54
import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
56
import com.typesafe.config.Config;
57
import com.typesafe.config.ConfigFactory;
58

59
@Mojo(threadSafe = true, name = "validate", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
60
public class ZallyMojo extends AbstractMojo
61
{
62
    private final ObjectMapper mapper;
63

64
    @Parameter(required = true, defaultValue = "${project.basedir}/src/main/resources/api.yaml", property = "zally.source")
65
    private String source;
66

67
    @Parameter(property = "zally.failOn")
68
    private List<Severity> failOn;
69

70
    @Parameter(property = "zally.resultFile")
71
    private String resultFile;
72

73
    @Parameter(property = "zally.skip", defaultValue = "false")
74
    private boolean skip;
75

76
    @Parameter(defaultValue = "${project}", required = true, readonly = true)
77
    private MavenProject project;
78

79
    @Parameter(property = "zally.ruleConfigs")
80
    private Map<String, String> ruleConfigs;
81

82
    @Parameter(property = "zally.rulesConfigLocation")
83
    private String rulesConfigLocation;
84

85
    @Parameter(property = "zally.skipRules")
86
    private Set<String> skipRules;
87

88
    public ZallyMojo()
89
    {
1✔
90
        mapper = new ObjectMapper(new YAMLFactory());
1✔
91
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
1✔
92
    }
1✔
93

94
    @Override
95
    public void execute() throws MojoFailureException
96
    {
97
        if (skip)
1✔
98
        {
99
            getLog().info("Skipping execution as requested");
×
100
            return;
×
101
        }
102

103
        Config config = parseConfigMap(ruleConfigs);
1✔
104
        if (rulesConfigLocation != null)
1✔
105
        {
106
            Path rulesConfigPath = Paths.get(rulesConfigLocation);
×
107
            if (!Files.exists(rulesConfigPath))
×
108
            {
109
                throw new MojoFailureException("The specified rules config file could not be found: " + rulesConfigLocation);
×
110
            }
111
            config = config.withFallback(ConfigFactory.parseFile(rulesConfigPath.toFile()).resolve());
×
112
        }
113
        config = config.withFallback(ConfigFactory.load("reference"));
1✔
114

115
        final ZallyRunner zallyRunner = new ZallyRunner(config, getLog());
1✔
116

117
        final boolean existsOnClassPath = getClass().getClassLoader().getResourceAsStream(source) != null;
1✔
118
        final boolean existsOnFilesystem = Files.exists(Paths.get(source));
1✔
119
        if (!existsOnClassPath && !existsOnFilesystem)
1✔
120
        {
121
            throw new MojoFailureException("The specified source file could not be found: " + source);
1✔
122
        }
123

124
        printInfo("Validating file '" + source + "'");
×
125

126
        if (!failOn.isEmpty())
×
127
        {
128
            getLog().info("Will fail build on errors of severity: " + failOn
×
129
                    .stream()
×
130
                    .map(Enum::name)
×
131
                    .collect(Collectors.joining(", ")));
×
132
        }
133
        else
134
        {
135
            getLog().warn("No errors will fail the build, reporting only. Adjust 'failOn' " +
×
136
                    "property to fail on requested severities:" + Arrays.toString(Severity.values()));
×
137
        }
138

139
        printErrorDescriptionsWithLink(zallyRunner.getRules());
×
140

141
        printSkippedRulesInfo(zallyRunner.getRules());
×
142
        final Map<CheckDetails, List<Result>> results = validate(zallyRunner, skipRules, source);
×
143

144
        // Map results to severity
145
        final Map<Severity, Map<CheckDetails, List<Result>>> resultsBySeverity = new LinkedHashMap<>();
×
146
        results.forEach((details, resultList) ->
×
147
        {
148
            for (final Result result : resultList)
×
149
            {
150
                resultsBySeverity.compute(result.getViolationType(), (severity, resultsByDetail) ->
×
151
                {
152
                    if (resultsByDetail == null)
×
153
                    {
154
                        resultsByDetail = new LinkedHashMap<>();
×
155
                    }
156
                    resultsByDetail.compute(details, (cd, rs) ->
×
157
                    {
158
                        if (rs == null)
×
159
                        {
160
                            rs = new LinkedList<>();
×
161
                        }
162
                        rs.add(result);
×
163
                        return rs;
×
164
                    });
165

166
                    return resultsByDetail;
×
167
                });
168
            }
×
169
        });
×
170

171
        printErrors(gatherViolations(resultsBySeverity));
×
172

173
        writeResults(gatherViolations(resultsBySeverity));
×
174

175
        // Check if we should halt the build due to validation errors
176
        for (Severity severity : failOn)
×
177
        {
178
            final int size = Optional.ofNullable(resultsBySeverity.get(severity))
×
179
                    .map(Map::size)
×
180
                    .orElse(0);
×
181
            if (size > 0)
×
182
            {
183
                throw new MojoFailureException("Failing build due to errors with severity " + severity);
×
184
            }
185
        }
×
186
    }
×
187

188
    private void printInfo(String message)
189
    {
190
        getLog().info("");
×
191
        getLog().info(message);
×
192
    }
×
193

194
    private void printErrors(List<String> violations)
195
    {
196
        printHeader("Rule violations (" + violations.size() + ")");
×
197
        violations.forEach(v -> getLog().warn(v));
×
198
        getLog().warn("");
×
199
    }
×
200

201
    private void printHeader(String message)
202
    {
203
        getLog().info("");
×
204
        getLog().info(message);
×
205
        getLog().info(StringUtils.repeat("-", message.length()));
×
206
    }
×
207

208
    private Config parseConfigMap(Map<String, String> ruleConfig)
209
    {
210
        final Map<String, String> m = ruleConfig != null ? ruleConfig : new TreeMap<>();
1✔
211
        final Map<String, Map<?, ?>> configurations = new LinkedHashMap<>();
1✔
212
        for (Map.Entry<String, String> e : m.entrySet())
1✔
213
        {
214
            final Map<?, ?> config = loadConfig(e.getKey(), e.getValue());
1✔
215
            Optional.ofNullable(config).ifPresent(c -> configurations.put(e.getKey(), c));
1✔
216
        }
1✔
217
        return ConfigFactory.parseMap(configurations);
1✔
218
    }
219

220
    private void printErrorDescriptionsWithLink(List<RuleDetails> rules)
221
    {
222
        final List<String> errorDescriptionsWithLink = rules
×
223
                .stream()
×
224
                .map(rule ->
×
225
                        rule.getRule().id() + " - "
×
226
                                + rule.getInstance().getClass().getSimpleName() + " - "
×
227
                                + rule.getRule().severity().name() + " - "
×
228
                                + rule.getRule().title() + " - "
×
229
                                + rule.getRuleSet().getUrl()).sorted()
×
230
                .collect(Collectors.toList());
×
231

232
        printHeader("Rules (" + rules.size() + ")");
×
233
        errorDescriptionsWithLink.forEach(i -> getLog().info(i));
×
234
    }
×
235

236
    private void printSkippedRulesInfo(List<RuleDetails> rules)
237
    {
238
        final Set<String> skipped = new LinkedHashSet<>();
×
239
        skipRules.forEach(ruleName ->
×
240
        {
241
            if (rules.stream().anyMatch(r ->
×
242
            {
243
                final String ruleClassName = r.getInstance().getClass().getSimpleName();
×
244
                final boolean ruleNameMatch = ruleClassName.equals(ruleName);
×
245
                final boolean isSkipped = skipRules.contains(ruleClassName);
×
246
                return ruleNameMatch && isSkipped;
×
247
            }))
248
            {
249
                skipped.add(ruleName);
×
250
            }
251
            else
252
            {
253
                getLog().warn("Requested to skip rule '" + ruleName + "', but no such rule is known.");
×
254
            }
255
        });
×
256

257
        final List<String> skippedDescription =
×
258
                rules
259
                        .stream()
×
260
                        .filter(r -> skipped.contains(r.getInstance().getClass().getSimpleName()))
×
261
                        .sorted(Comparator.comparing(a -> a.getRule().id()))
×
262
                        .map(d -> d.getRule().id() + " - " + d.getInstance().getClass().getSimpleName() + " - " + d.getRule().severity() + " - " + d.getRule().title())
×
263
                        .collect(Collectors.toList());
×
264

265
        if (!skippedDescription.isEmpty())
×
266
        {
267
            printHeader("Skipped rules (" + skippedDescription.size() + ")");
×
268
            skippedDescription.forEach(i -> getLog().info(i));
×
269
        }
270
    }
×
271

272
    private Map<String, Object> loadConfig(final String ruleName, final String ruleConfig)
273
    {
274
        try
275
        {
276
            return mapper.readValue(ruleConfig, Map.class);
1✔
277
        }
278
        catch (JsonProcessingException e)
×
279
        {
280
            throw new UncheckedIOException("Unable to parse configuration for rule name " + ruleName, e);
×
281
        }
282
    }
283

284
    private void writeResults(List<String> violations)
285
    {
286
        if (resultFile != null && !resultFile.trim().equals(""))
×
287
        {
288
            try
289
            {
290
                printInfo("Writing result file to " + resultFile);
×
291
                getLog().info("");
×
292
                final Path target = Paths.get(resultFile);
×
293
                Files.createDirectories(target.toAbsolutePath().getParent());
×
294
                Files.writeString(target, "Rule violations (" + violations.size() + ")" + System.lineSeparator());
×
295
                violations.forEach(v -> {
×
296
                    try {
297
                        Files.writeString(target, v + System.lineSeparator() , StandardOpenOption.APPEND);
×
298
                    } catch (IOException e) {
×
299
                        getLog().error("Could not write full output File", e);
×
300
                        e.printStackTrace();
×
301
                    }
×
302
                });
×
303
            }
304
            catch (IOException e)
×
305
            {
306
                getLog().error(e);
×
307
                throw new UncheckedIOException(e);
×
308
            }
×
309
        }
310
    }
×
311

312
    private Map<CheckDetails, List<Result>> validate(ZallyRunner zallyRunner, final Set<String> skipped, String url)
313
    {
314
        try
315
        {
316
            return zallyRunner.validate(url, skipped);
×
317
        }
318
        catch (IOException e)
×
319
        {
320
            throw new UncheckedIOException(e.getMessage(), e);
×
321
        }
322
    }
323

324
    private List<String> gatherViolations(Map<Severity, Map<CheckDetails, List<Result>>> results){
325
        final List<String> violations = new ArrayList<>();
×
326
        results.forEach((severity, res) ->
×
327
                res.forEach((checkDetails, resultList) ->
×
328
                        resultList.forEach(result ->
×
329
                                violations.add(checkDetails.getRule().id()
×
330
                                        + " - " + severity
331
                                        + " - " + checkDetails.getInstance().getClass().getSimpleName()
×
332
                                        + " - " + result.getDescription()
×
333
                                        + " - " + result.getPointer()
×
334
                                        + " - " + result.getLines()))));
×
335
        return violations;
×
336
    }
337
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc