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

devonfw / IDEasy / 15217312591

23 May 2025 07:01PM UTC coverage: 67.574% (-0.3%) from 67.878%
15217312591

Pull #1334

github

web-flow
Merge 290a53440 into 17c699cdf
Pull Request #1334: #1332: fixed bug pattern, proper Step usage, allow running tool if plugin failed

3152 of 5064 branches covered (62.24%)

Branch coverage included in aggregate %.

8049 of 11512 relevant lines covered (69.92%)

3.07 hits per line

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

68.97
cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java
1
package com.devonfw.tools.ide.tool.mvn;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.security.SecureRandom;
6
import java.util.Base64;
7
import java.util.LinkedHashSet;
8
import java.util.Set;
9
import java.util.regex.Matcher;
10

11
import com.devonfw.tools.ide.common.Tag;
12
import com.devonfw.tools.ide.context.IdeContext;
13
import com.devonfw.tools.ide.git.GitContext;
14
import com.devonfw.tools.ide.io.FileAccess;
15
import com.devonfw.tools.ide.process.ProcessContext;
16
import com.devonfw.tools.ide.process.ProcessErrorHandling;
17
import com.devonfw.tools.ide.process.ProcessMode;
18
import com.devonfw.tools.ide.process.ProcessResult;
19
import com.devonfw.tools.ide.step.Step;
20
import com.devonfw.tools.ide.tool.ToolCommandlet;
21
import com.devonfw.tools.ide.tool.plugin.PluginBasedCommandlet;
22
import com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor;
23
import com.devonfw.tools.ide.variable.IdeVariables;
24
import com.devonfw.tools.ide.variable.VariableSyntax;
25

26
/**
27
 * {@link ToolCommandlet} for <a href="https://maven.apache.org/">maven</a>.
28
 */
29
public class Mvn extends PluginBasedCommandlet {
30

31
  /**
32
   * The name of the mvn folder
33
   */
34
  public static final String MVN_CONFIG_FOLDER = "mvn";
35

36
  /**
37
   * The name of the m2 repository
38
   */
39
  public static final String MVN_CONFIG_LEGACY_FOLDER = ".m2";
40

41
  /** The name of the settings-security.xml */
42
  public static final String SETTINGS_SECURITY_FILE = "settings-security.xml";
43

44
  /**
45
   * The name of the settings.xml
46
   */
47
  public static final String SETTINGS_FILE = "settings.xml";
48

49
  private static final String DOCUMENTATION_PAGE_CONF = "https://github.com/devonfw/IDEasy/blob/main/documentation/conf.adoc";
50

51
  private static final String ERROR_SETTINGS_FILE_MESSAGE =
52
      "Failed to create settings file at: {}. For further details see:\n" + DOCUMENTATION_PAGE_CONF;
53

54
  private static final String ERROR_SETTINGS_SECURITY_FILE_MESSAGE =
55
      "Failed to create settings security file at: {}. For further details see:\n" + DOCUMENTATION_PAGE_CONF;
56

57
  private static final VariableSyntax VARIABLE_SYNTAX = VariableSyntax.SQUARE;
3✔
58

59
  /**
60
   * The constructor.
61
   *
62
   * @param context the {@link IdeContext}.
63
   */
64
  public Mvn(IdeContext context) {
65

66
    super(context, "mvn", Set.of(Tag.JAVA, Tag.BUILD));
7✔
67
  }
1✔
68

69
  @Override
70
  public void postInstall() {
71

72
    // locate templates...
73
    Path templatesConfMvnFolder = getMavenTemplatesFolder();
3✔
74
    if (templatesConfMvnFolder == null) {
2✔
75
      return;
1✔
76
    }
77
    // locate real config...
78
    boolean legacy = templatesConfMvnFolder.getFileName().toString().equals(MVN_CONFIG_LEGACY_FOLDER);
6✔
79
    Path mvnConfigPath = getMavenConfFolder(legacy);
4✔
80

81
    Path settingsSecurityFile = mvnConfigPath.resolve(SETTINGS_SECURITY_FILE);
4✔
82
    createSettingsSecurityFile(settingsSecurityFile);
3✔
83

84
    Path settingsFile = mvnConfigPath.resolve(SETTINGS_FILE);
4✔
85
    createSettingsFile(settingsFile, templatesConfMvnFolder.resolve(SETTINGS_FILE), settingsSecurityFile);
7✔
86
  }
1✔
87

88
  private void createSettingsSecurityFile(Path settingsSecurityFile) {
89

90
    if (Files.exists(settingsSecurityFile)) {
5!
91
      return; // file already exists, nothing to do...
×
92
    }
93
    Step step = this.context.newStep("Create mvn settings security file at " + settingsSecurityFile);
7✔
94
    step.run(() -> doCreateSettingsSecurityFileStep(settingsSecurityFile, step));
12✔
95
  }
1✔
96

97
  private void doCreateSettingsSecurityFileStep(Path settingsSecurityFile, Step step) {
98

99
    SecureRandom secureRandom = new SecureRandom();
4✔
100
    byte[] randomBytes = new byte[20];
3✔
101

102
    secureRandom.nextBytes(randomBytes);
3✔
103
    String base64String = Base64.getEncoder().encodeToString(randomBytes);
4✔
104

105
    String encryptedMasterPassword = retrievePassword("--encrypt-master-password", base64String);
5✔
106

107
    String settingsSecurityXml =
3✔
108
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<settingsSecurity>\n" + "  <master>" + encryptedMasterPassword + "</master>\n"
109
            + "</settingsSecurity>";
110
    try {
111
      this.context.getFileAccess().writeFileContent(settingsSecurityXml, settingsSecurityFile, true);
7✔
112
    } catch (Exception e) {
×
113
      step.error(e, ERROR_SETTINGS_SECURITY_FILE_MESSAGE, settingsSecurityFile);
×
114
    }
1✔
115
  }
1✔
116

117
  private void createSettingsFile(Path settingsFile, Path settingsTemplateFile, Path settingsSecurityFile) {
118

119
    if (Files.exists(settingsFile)) {
5!
120
      return;
×
121
    }
122
    if (!Files.exists(settingsTemplateFile)) {
5!
123
      this.context.warning("Missing maven settings template at {}. ", settingsTemplateFile);
×
124
      return;
×
125
    }
126
    Step step = this.context.newStep("Create mvn settings file at " + settingsFile);
7✔
127
    step.run(() -> doCreateSettingsFile(settingsFile, settingsTemplateFile, settingsSecurityFile, step));
16✔
128
  }
1✔
129

130
  private void doCreateSettingsFile(Path settingsFile, Path settingsTemplateFile, Path settingsSecurityFile, Step step) {
131

132
    try {
133
      FileAccess fileAccess = this.context.getFileAccess();
4✔
134
      String content = fileAccess.readFileContent(settingsTemplateFile);
4✔
135
      GitContext gitContext = this.context.getGitContext();
4✔
136
      String gitSettingsUrl = gitContext.retrieveGitUrl(this.context.getSettingsPath());
6✔
137
      if (gitSettingsUrl == null) {
2!
138
        this.context.warning("Failed to determine git remote URL for settings folder.");
×
139
      } else if (!gitSettingsUrl.equals(GitContext.DEFAULT_SETTINGS_GIT_URL) && Files.exists(settingsSecurityFile)) {
9!
140
        Set<String> variables = findVariables(content);
4✔
141
        for (String variable : variables) {
10✔
142
          String secret = getEncryptedPassword(variable);
4✔
143
          content = content.replace(VARIABLE_SYNTAX.create(variable), secret);
7✔
144
        }
1✔
145
      }
146
      fileAccess.writeFileContent(content, settingsFile, true);
5✔
147
      step.success();
2✔
148
    } catch (Exception e) {
×
149
      step.error(e, ERROR_SETTINGS_FILE_MESSAGE, settingsFile);
×
150
    }
1✔
151
  }
1✔
152

153
  private String getEncryptedPassword(String variable) {
154

155
    String input = this.context.askForInput("Please enter secret value for variable " + variable + ":");
6✔
156

157
    String encryptedPassword = retrievePassword("--encrypt-password", input);
5✔
158
    this.context.info("Encrypted as " + encryptedPassword);
5✔
159

160
    return encryptedPassword;
2✔
161
  }
162

163
  private String retrievePassword(String args, String input) {
164

165
    ProcessResult result = runTool(ProcessMode.DEFAULT_CAPTURE, ProcessErrorHandling.LOG_WARNING, this.context.newProcess(), args, input,
21✔
166
        getSettingsSecurityProperty());
2✔
167

168
    return result.getSingleOutput(null);
4✔
169
  }
170

171
  private Set<String> findVariables(String content) {
172

173
    Set<String> variables = new LinkedHashSet<>();
4✔
174
    Matcher matcher = VARIABLE_SYNTAX.getPattern().matcher(content);
5✔
175
    while (matcher.find()) {
3✔
176
      String variableName = VARIABLE_SYNTAX.getVariable(matcher);
4✔
177
      variables.add(variableName);
4✔
178
    }
1✔
179
    return variables;
2✔
180
  }
181

182
  @Override
183
  public boolean installPlugin(ToolPluginDescriptor plugin, Step step, ProcessContext pc) {
184

185
    Path mavenPlugin = this.getToolPath().resolve("lib/ext/" + plugin.name() + ".jar");
×
186
    this.context.getFileAccess().download(plugin.url(), mavenPlugin);
×
187

188
    if (Files.exists(mavenPlugin)) {
×
189
      this.context.success("Successfully added {} to {}", plugin.name(), mavenPlugin.toString());
×
190
      step.success();
×
191
      return true;
×
192
    } else {
193
      step.error("Plugin {} has wrong properties\n" //
×
194
          + "Please check the plugin properties file in {}", mavenPlugin.getFileName(), mavenPlugin.toAbsolutePath());
×
195
      return false;
×
196
    }
197
  }
198

199
  @Override
200
  public String getToolHelpArguments() {
201

202
    return "-h";
2✔
203
  }
204

205
  /**
206
   * @return the {@link Path} to the folder with the maven configuration templates.
207
   */
208
  public Path getMavenTemplatesFolder() {
209

210
    Path templatesFolder = this.context.getSettingsTemplatePath();
4✔
211
    if (templatesFolder == null) {
2✔
212
      return null;
2✔
213
    }
214
    Path templatesConfFolder = templatesFolder.resolve(IdeContext.FOLDER_CONF);
4✔
215
    Path templatesConfMvnFolder = templatesConfFolder.resolve(MVN_CONFIG_FOLDER);
4✔
216
    if (!Files.isDirectory(templatesConfMvnFolder)) {
5!
217
      Path templatesConfMvnLegacyFolder = templatesConfFolder.resolve(MVN_CONFIG_LEGACY_FOLDER);
×
218
      if (!Files.isDirectory(templatesConfMvnLegacyFolder)) {
×
219
        this.context.warning("No maven templates found neither in {} nor in {} - configuration broken", templatesConfMvnFolder,
×
220
            templatesConfMvnLegacyFolder);
221
        return null;
×
222
      }
223
      templatesConfMvnFolder = templatesConfMvnLegacyFolder;
×
224
    }
225
    return templatesConfMvnFolder;
2✔
226
  }
227

228
  /**
229
   * @param legacy - {@code true} to enforce legacy fallback creation, {@code false} otherwise.
230
   * @return the {@link Path} to the maven configuration folder (where "settings.xml" can be found).
231
   */
232
  public Path getMavenConfFolder(boolean legacy) {
233

234
    Path confPath = this.context.getConfPath();
4✔
235
    Path mvnConfigFolder = confPath.resolve(MVN_CONFIG_FOLDER);
4✔
236
    if (!Files.isDirectory(mvnConfigFolder)) {
5✔
237
      Path mvnConfigLegacyFolder = confPath.resolve(Mvn.MVN_CONFIG_LEGACY_FOLDER);
4✔
238
      if (Files.isDirectory(mvnConfigLegacyFolder)) {
5!
239
        mvnConfigFolder = mvnConfigLegacyFolder;
×
240
      } else {
241
        if (legacy) {
2!
242
          mvnConfigFolder = mvnConfigLegacyFolder;
×
243
        }
244
        this.context.getFileAccess().mkdirs(mvnConfigFolder);
5✔
245
      }
246
    }
247
    return mvnConfigFolder;
2✔
248
  }
249

250
  /**
251
   * @return the maven arguments (MVN_ARGS).
252
   */
253
  public String getMavenArgs() {
254
    Path mavenConfFolder = getMavenConfFolder(false);
4✔
255
    Path mvnSettingsFile = mavenConfFolder.resolve(Mvn.SETTINGS_FILE);
4✔
256
    Path settingsSecurityFile = mavenConfFolder.resolve(SETTINGS_SECURITY_FILE);
4✔
257
    boolean settingsFileExists = Files.exists(mvnSettingsFile);
5✔
258
    boolean securityFileExists = Files.exists(settingsSecurityFile);
5✔
259
    if (!settingsFileExists && !securityFileExists) {
4✔
260
      return null;
2✔
261
    }
262
    StringBuilder sb = new StringBuilder();
4✔
263
    if (settingsFileExists) {
2✔
264
      sb.append("-s ");
4✔
265
      sb.append(mvnSettingsFile);
4✔
266
    }
267
    if (securityFileExists) {
2✔
268
      if (!sb.isEmpty()) {
3!
269
        sb.append(" ");
×
270
      }
271
      sb.append(getSettingsSecurityProperty());
5✔
272
    }
273
    return sb.toString();
3✔
274
  }
275

276
  private String getSettingsSecurityProperty() {
277
    return "-Dsettings.security=" + this.context.getMavenConfigurationFolder().resolve(SETTINGS_SECURITY_FILE).toString().replace("\\", "\\\\");
11✔
278
  }
279

280
  /**
281
   * @return the {@link Path} to the local maven repository.
282
   */
283
  public Path getLocalRepository() {
284
    return IdeVariables.M2_REPO.get(this.context);
×
285
  }
286

287
  /**
288
   * @param artifact the {@link MvnArtifact}.
289
   */
290
  public void downloadArtifact(MvnArtifact artifact) {
291

292
    this.context.newStep("Download artifact " + artifact).run(() -> {
×
293
      runTool("dependency:get", "-Dartifact=" + artifact.getKey());
×
294
    });
×
295
  }
×
296

297
  /**
298
   * @param artifact the {@link MvnArtifact}.
299
   * @return the {@link Path} to the {@link MvnArtifact} that was downloaded if not already present.
300
   */
301
  public Path getOrDownloadArtifact(MvnArtifact artifact) {
302

303
    Path artifactPath = getLocalRepository().resolve(artifact.getPath());
×
304
    if (!Files.exists(artifactPath)) {
×
305
      downloadArtifact(artifact);
×
306
      assert (Files.exists(artifactPath));
×
307
    }
308
    return artifactPath;
×
309
  }
310
}
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

© 2026 Coveralls, Inc