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

devonfw / IDEasy / 9778043214

03 Jul 2024 12:41PM UTC coverage: 60.631% (+0.5%) from 60.142%
9778043214

push

github

web-flow
#36: Tool Commandlet for tomcat (#250)

1939 of 3515 branches covered (55.16%)

Branch coverage included in aggregate %.

5133 of 8149 relevant lines covered (62.99%)

2.76 hits per line

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

82.99
cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import com.devonfw.tools.ide.common.Tag;
4
import com.devonfw.tools.ide.context.IdeContext;
5
import com.devonfw.tools.ide.io.FileAccess;
6
import com.devonfw.tools.ide.io.FileCopyMode;
7
import com.devonfw.tools.ide.log.IdeLogLevel;
8
import com.devonfw.tools.ide.process.ProcessContext;
9
import com.devonfw.tools.ide.process.ProcessErrorHandling;
10
import com.devonfw.tools.ide.process.ProcessMode;
11
import com.devonfw.tools.ide.repo.ToolRepository;
12
import com.devonfw.tools.ide.step.Step;
13
import com.devonfw.tools.ide.url.model.file.dependencyJson.DependencyInfo;
14
import com.devonfw.tools.ide.version.VersionIdentifier;
15

16
import java.io.IOException;
17
import java.nio.file.Files;
18
import java.nio.file.Path;
19
import java.nio.file.StandardOpenOption;
20
import java.util.HashMap;
21
import java.util.List;
22
import java.util.Set;
23

24
/**
25
 * {@link ToolCommandlet} that is installed locally into the IDE.
26
 */
27
public abstract class LocalToolCommandlet extends ToolCommandlet {
1✔
28

29
  protected HashMap<String, String> dependenciesEnvVariableNames = null;
3✔
30

31
  protected HashMap<String, String> dependenciesEnvVariablePaths = new HashMap<>();
5✔
32

33
  private final Dependency dependency = new Dependency(this.context, this.tool);
9✔
34

35
  /**
36
   * The constructor.
37
   *
38
   * @param context the {@link IdeContext}.
39
   * @param tool    the {@link #getName() tool name}.
40
   * @param tags    the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
41
   */
42
  public LocalToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {
43

44
    super(context, tool, tags);
5✔
45
  }
1✔
46

47
  /**
48
   * @return the {@link Path} where the tool is located (installed).
49
   */
50
  public Path getToolPath() {
51

52
    return this.context.getSoftwarePath().resolve(getName());
7✔
53
  }
54

55
  /**
56
   * @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside {@link #getToolPath() tool path}.
57
   */
58
  public Path getToolBinPath() {
59

60
    Path toolPath = getToolPath();
3✔
61
    Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"), false);
14✔
62
    if ((binPath != null) && Files.isDirectory(binPath)) {
7!
63
      return binPath;
2✔
64
    }
65
    return toolPath;
×
66
  }
67

68
  @Override
69
  protected boolean doInstall(boolean silent) {
70

71
    VersionIdentifier configuredVersion = getConfiguredVersion();
3✔
72
    // get installed version before installInRepo actually may install the software
73
    VersionIdentifier installedVersion = getInstalledVersion();
3✔
74
    Step step = this.context.newStep(silent, "Install " + this.tool, configuredVersion);
14✔
75
    try {
76
      // install configured version of our tool in the software repository if not already installed
77
      ToolInstallation installation = installInRepo(configuredVersion);
4✔
78
      // check if we already have this version installed (linked) locally in IDE_HOME/software
79
      VersionIdentifier resolvedVersion = installation.resolvedVersion();
3✔
80
      if (resolvedVersion.equals(installedVersion) && !installation.newInstallation()) {
7!
81
        IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO;
6✔
82
        this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, getToolWithEdition());
18✔
83
        step.success();
2✔
84
        return false;
4✔
85
      }
86
      // we need to link the version or update the link.
87
      Path toolPath = getToolPath();
3✔
88
      FileAccess fileAccess = this.context.getFileAccess();
4✔
89
      if (Files.exists(toolPath)) {
5✔
90
        fileAccess.backup(toolPath);
3✔
91
      }
92
      fileAccess.mkdirs(toolPath.getParent());
4✔
93
      fileAccess.symlink(installation.linkDir(), toolPath);
5✔
94
      this.context.getPath().setPath(this.tool, installation.binDir());
8✔
95
      postInstall();
2✔
96
      if (installedVersion == null) {
2!
97
        step.success("Successfully installed {} in version {}", this.tool, resolvedVersion);
15✔
98
      } else {
99
        step.success("Successfully installed {} in version {} replacing previous version {}", this.tool, resolvedVersion, installedVersion);
×
100
      }
101
      return true;
4✔
102
    } catch (RuntimeException e) {
×
103
      step.error(e, true);
×
104
      throw e;
×
105
    } finally {
106
      step.close();
2✔
107
    }
108

109
  }
110

111
  /**
112
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
113
   * repository without touching the IDE installation.
114
   *
115
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
116
   * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
117
   */
118
  public ToolInstallation installInRepo(VersionIdentifier version) {
119

120
    return installInRepo(version, getEdition());
6✔
121
  }
122

123
  /**
124
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
125
   * repository without touching the IDE installation.
126
   *
127
   * @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
128
   * @param edition the specific edition to install.
129
   * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
130
   */
131
  public ToolInstallation installInRepo(VersionIdentifier version, String edition) {
132

133
    return installInRepo(version, edition, this.context.getDefaultToolRepository());
8✔
134
  }
135

136
  /**
137
   * Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
138
   * repository without touching the IDE installation.
139
   *
140
   * @param version        the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
141
   * @param edition        the specific edition to install.
142
   * @param toolRepository the {@link ToolRepository} to use.
143
   * @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
144
   */
145
  public ToolInstallation installInRepo(VersionIdentifier version, String edition, ToolRepository toolRepository) {
146

147
    VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version);
7✔
148

149
    if (Files.exists(this.dependency.getDependencyJsonPath(getEdition()))) {
9✔
150
      installDependencies(resolvedVersion);
4✔
151
    } else {
152
      this.context.trace("No Dependencies file found");
4✔
153
    }
154

155
    Path toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool).resolve(edition)
12✔
156
            .resolve(resolvedVersion.toString());
3✔
157
    Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
4✔
158
    FileAccess fileAccess = this.context.getFileAccess();
4✔
159
    if (Files.isDirectory(toolPath)) {
5✔
160
      if (Files.exists(toolVersionFile)) {
5!
161
        if (this.context.isForceMode()) {
4!
162
          fileAccess.delete(toolPath);
×
163
        } else {
164
          this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion, getToolWithEdition(this.tool, edition), toolPath);
21✔
165
          return createToolInstallation(toolPath, resolvedVersion, toolVersionFile);
6✔
166
        }
167
      } else {
168
        this.context.warning("Deleting corrupted installation at {}", toolPath);
×
169
        fileAccess.delete(toolPath);
×
170
      }
171
    }
172
    Path target = toolRepository.download(this.tool, edition, resolvedVersion);
7✔
173
    fileAccess.mkdirs(toolPath.getParent());
4✔
174
    boolean extract = isExtract();
3✔
175
    if (!extract) {
2✔
176
      this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, target);
15✔
177
    }
178
    fileAccess.extract(target, toolPath, this::postExtract, extract);
7✔
179
    try {
180
      Files.writeString(toolVersionFile, resolvedVersion.toString(), StandardOpenOption.CREATE_NEW);
11✔
181
    } catch (IOException e) {
×
182
      throw new IllegalStateException("Failed to write version file " + toolVersionFile, e);
×
183
    }
1✔
184
    // newInstallation results in above conditions to be true if isForceMode is true or if the tool version file was
185
    // missing
186
    return createToolInstallation(toolPath, resolvedVersion, toolVersionFile, true);
7✔
187
  }
188

189
  /**
190
   * Post-extraction hook that can be overridden to add custom processing after unpacking and before moving to the final destination folder.
191
   *
192
   * @param extractedDir the {@link Path} to the folder with the unpacked tool.
193
   */
194
  protected void postExtract(Path extractedDir) {
195

196
  }
1✔
197

198
  @Override
199
  public VersionIdentifier getInstalledVersion() {
200

201
    return getInstalledVersion(this.context.getSoftwarePath().resolve(getName()));
9✔
202
  }
203

204
  /**
205
   * @param toolPath the installation {@link Path} where to find the version file.
206
   * @return the currently installed {@link VersionIdentifier version} of this tool or {@code null} if not installed.
207
   */
208
  protected VersionIdentifier getInstalledVersion(Path toolPath) {
209

210
    if (!Files.isDirectory(toolPath)) {
5✔
211
      this.context.debug("Tool {} not installed in {}", getName(), toolPath);
15✔
212
      return null;
2✔
213
    }
214
    Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
4✔
215
    if (!Files.exists(toolVersionFile)) {
5✔
216
      Path legacyToolVersionFile = toolPath.resolve(IdeContext.FILE_LEGACY_SOFTWARE_VERSION);
4✔
217
      if (Files.exists(legacyToolVersionFile)) {
5✔
218
        toolVersionFile = legacyToolVersionFile;
3✔
219
      } else {
220
        this.context.warning("Tool {} is missing version file in {}", getName(), toolVersionFile);
15✔
221
        return null;
2✔
222
      }
223
    }
224
    try {
225
      String version = Files.readString(toolVersionFile).trim();
4✔
226
      return VersionIdentifier.of(version);
3✔
227
    } catch (IOException e) {
×
228
      throw new IllegalStateException("Failed to read file " + toolVersionFile, e);
×
229
    }
230
  }
231

232
  @Override
233
  public String getInstalledEdition() {
234

235
    return getInstalledEdition(this.context.getSoftwarePath().resolve(getName()));
9✔
236
  }
237

238
  /**
239
   * @param toolPath the installation {@link Path} where to find currently installed tool. The name of the parent
240
   *     directory of the real path corresponding to the passed {@link Path path} must be the name of the edition.
241
   * @return the installed edition of this tool or {@code null} if not installed.
242
   */
243
  public String getInstalledEdition(Path toolPath) {
244

245
    if (!Files.isDirectory(toolPath)) {
5!
246
      this.context.debug("Tool {} not installed in {}", getName(), toolPath);
×
247
      return null;
×
248
    }
249
    try {
250
      String edition = toolPath.toRealPath().getParent().getFileName().toString();
8✔
251
      if (!this.context.getUrls().getSortedEditions(getName()).contains(edition)) {
9!
252
        edition = getEdition();
3✔
253
      }
254
      return edition;
2✔
255
    } catch (IOException e) {
×
256
      throw new IllegalStateException(
×
257
          "Couldn't determine the edition of " + getName() + " from the directory structure of its software path "
×
258
              + toolPath
259
              + ", assuming the name of the parent directory of the real path of the software path to be the edition "
260
              + "of the tool.", e);
261
    }
262

263
  }
264

265
  public void uninstall() {
266

267
    try {
268
      Path softwarePath = getToolPath();
3✔
269
      if (Files.exists(softwarePath)) {
5✔
270
        try {
271
          context.getFileAccess().delete(softwarePath);
5✔
272
          this.context.success("Successfully uninstalled " + this.tool);
6✔
273
        } catch (Exception e) {
1✔
274
          this.context.error("Couldn't uninstall " + this.tool);
6✔
275
        }
2✔
276
      } else {
277
        this.context.warning("An installed version of " + this.tool + " does not exist");
6✔
278
      }
279
    } catch (Exception e) {
×
280
      this.context.error(e.getMessage());
×
281
    }
1✔
282
  }
1✔
283

284
  private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile,
285
                                                  boolean newInstallation) {
286

287
    Path linkDir = getMacOsHelper().findLinkDir(rootDir, this.tool);
7✔
288
    Path binDir = linkDir;
2✔
289
    Path binFolder = binDir.resolve(IdeContext.FOLDER_BIN);
4✔
290
    if (Files.isDirectory(binFolder)) {
5✔
291
      binDir = binFolder;
2✔
292
    }
293
    if (linkDir != rootDir) {
3✔
294
      assert (!linkDir.equals(rootDir));
5!
295
      this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE);
7✔
296
    }
297
    return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion, newInstallation);
9✔
298
  }
299

300
  private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile) {
301

302
    return createToolInstallation(rootDir, resolvedVersion, toolVersionFile, false);
7✔
303
  }
304

305
  @Override
306
  public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) {
307

308
    Path binaryPath;
309
    Path toolPath = Path.of(getBinaryName());
6✔
310
    if (toolVersion == null) {
2!
311
      install(true);
4✔
312
      binaryPath = toolPath;
3✔
313
    } else {
314
      throw new UnsupportedOperationException("Not yet implemented!");
×
315
    }
316

317
    if (Files.exists(this.dependency.getDependencyJsonPath(getEdition()))) {
9✔
318
      setDependencyRepository(getInstalledVersion());
5✔
319
    } else {
320
      this.context.trace("No Dependencies file found");
4✔
321
    }
322

323
    ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath).addArgs(args);
10✔
324

325
    for (String key : this.dependenciesEnvVariablePaths.keySet()) {
12✔
326

327
      String dependencyPath = this.dependenciesEnvVariablePaths.get(key);
6✔
328
      pc = pc.withEnvVar(key, dependencyPath);
5✔
329
    }
1✔
330

331
    pc.run(processMode);
4✔
332
  }
1✔
333

334
  private void installDependencies(VersionIdentifier version) {
335

336
    List<DependencyInfo> dependencies = this.dependency.readJson(version, getEdition());
7✔
337

338
    for (DependencyInfo dependencyInfo : dependencies) {
10✔
339

340
      String dependencyName = dependencyInfo.getTool();
3✔
341
      VersionIdentifier dependencyVersionToInstall = this.dependency.findDependencyVersionToInstall(dependencyInfo);
5✔
342
      if (dependencyVersionToInstall == null) {
2!
343
        continue;
×
344
      }
345

346
      ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
6✔
347
      Path dependencyRepository = getDependencySoftwareRepository(dependencyName, dependencyTool.getEdition());
6✔
348

349
      if (!Files.exists(dependencyRepository)) {
5!
350
        installDependencyInRepo(dependencyName, dependencyTool, dependencyVersionToInstall);
×
351
      } else {
352
        Path versionExistingInRepository = this.dependency.versionExistsInRepository(dependencyRepository, dependencyInfo.getVersionRange());
7✔
353
        if (versionExistingInRepository.equals(Path.of(""))) {
7✔
354
          installDependencyInRepo(dependencyName, dependencyTool, dependencyVersionToInstall);
6✔
355
        } else {
356
          this.context.info("Necessary version of the dependency {} is already installed in repository", dependencyName);
10✔
357
        }
358
      }
359
    }
1✔
360
  }
1✔
361

362
  private void installDependencyInRepo(String dependencyName, ToolCommandlet dependencyTool, VersionIdentifier dependencyVersionToInstall) {
363

364
    this.context.info("The version {} of the dependency {} is being installed", dependencyVersionToInstall, dependencyName);
14✔
365
    LocalToolCommandlet dependencyLocal = (LocalToolCommandlet) dependencyTool;
3✔
366
    dependencyLocal.installInRepo(dependencyVersionToInstall);
4✔
367
    this.context.info("The version {} of the dependency {} was successfully installed", dependencyVersionToInstall, dependencyName);
14✔
368
  }
1✔
369

370
  protected void setDependencyRepository(VersionIdentifier version) {
371

372
    List<DependencyInfo> dependencies = this.dependency.readJson(version, getEdition());
7✔
373

374
    for (DependencyInfo dependencyInfo : dependencies) {
10✔
375
      String dependencyName = dependencyInfo.getTool();
3✔
376
      VersionIdentifier dependencyVersionToInstall = this.dependency.findDependencyVersionToInstall(dependencyInfo);
5✔
377
      if (dependencyVersionToInstall == null) {
2!
378
        continue;
×
379
      }
380

381
      ToolCommandlet dependencyTool = this.context.getCommandletManager().getToolCommandlet(dependencyName);
6✔
382
      Path dependencyRepository = getDependencySoftwareRepository(dependencyName, dependencyTool.getEdition());
6✔
383
      Path versionExistingInRepository = this.dependency.versionExistsInRepository(dependencyRepository, dependencyInfo.getVersionRange());
7✔
384
      Path dependencyPath;
385

386
      if (versionExistingInRepository.equals(Path.of(""))) {
7!
387
        dependencyPath = dependencyRepository.resolve(dependencyVersionToInstall.toString());
×
388
      } else {
389
        dependencyPath = dependencyRepository.resolve(versionExistingInRepository);
4✔
390
      }
391
      setDependencyEnvironmentPath(getDependencyEnvironmentName(dependencyName), dependencyPath);
6✔
392
    }
1✔
393
  }
1✔
394

395
  private void setDependencyEnvironmentPath(String dependencyEnvironmentName, Path dependencyPath) {
396

397
    this.dependenciesEnvVariablePaths.put(dependencyEnvironmentName, dependencyPath.toString());
7✔
398

399
  }
1✔
400

401
  /**
402
   * Method to return the list of the environment variable name for the dependencies. If necessary, it should be overridden in the specific tool
403
   *
404
   * @return the {@link HashMap} with the dependency name mapped to the env variable, for example ( java: JAVA_HOME )
405
   */
406

407
  protected HashMap<String, String> listOfDependencyEnvVariableNames() {
408

409
    return dependenciesEnvVariableNames;
×
410
  }
411

412
  private String getDependencyEnvironmentName(String dependencyName) {
413

414
    HashMap<String, String> envVariableName = listOfDependencyEnvVariableNames();
3✔
415

416
    if (envVariableName != null) {
2!
417
      return envVariableName.get(dependencyName);
5✔
418
    }
419

420
    return dependencyName.toUpperCase() + "_HOME";
×
421
  }
422

423
  private Path getDependencySoftwareRepository(String dependencyName, String dependencyEdition) {
424

425
    String defaultToolRepositoryId = this.context.getDefaultToolRepository().getId();
5✔
426
    Path dependencyRepository = this.context.getSoftwareRepositoryPath().resolve(defaultToolRepositoryId).resolve(dependencyName).resolve(dependencyEdition);
10✔
427

428
    return dependencyRepository;
2✔
429
  }
430

431
}
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