com.teamscale.commons.toml.TeamscaleIntegrationConfigurationParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-commons Show documentation
Show all versions of teamscale-commons Show documentation
Provides common DTOs for Teamscale
package com.teamscale.commons.toml;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.fasterxml.jackson.dataformat.toml.TomlMapper;
import com.fasterxml.jackson.dataformat.toml.TomlStreamReadException;
import com.google.common.annotations.VisibleForTesting;
/**
* Parser that reads configuration files defining how source files on external
* systems can be mapped to uniform paths on a Teamscale server. For example,
* the format defines for a given source file as which uniform path on which
* Teamscale Server (url, project id, branch) the file is known by Teamscale.
*
* Use case: IDE plugins (which need to query and upload data for a given source
* file in an IDE project).
*
* The config files are named {@link #CONFIGURATION_FILE_NAME} and can be in a
* folder hierarchy. Config items not defined in a nested file can be defined in
* a file higher up in the hierarchy. See
* architecture-decisions/0016-ide-config-format.md for the details.
*/
public class TeamscaleIntegrationConfigurationParser {
/**
* The name of the config files in the hierarchy. According to the
* specification, all file names are ".teamscale.toml".
*/
private static final String CONFIGURATION_FILE_NAME = ".teamscale.toml";
/**
* Name of the "version" property. This must be the same as the field name
* {@link TeamscaleIntegrationConfigurationFileV1#version}. We need it here to
* read the version number from a toml file before deserializing it to a
* concrete-version class.
*/
public static final String FORMAT_VERSION_PROPERTY_NAME = "version";
/**
* Reads the "aggregated" configuration for the given folder on the local file
* system. This includes config files in parent directories of the given folder.
*/
public static Optional readAggregatedConfigurationForFolder(File initialFolder)
throws IOException {
return readAggregatedConfigurationForFolder(initialFolder, null);
}
/**
* Reads the "aggregated" configuration for the given folder on the local file
* system. This includes config files in parent directories of the given folder.
*
* Stops parent-file discovery when reaching the given #boundaryFolderName. This
* is necessary to avoid reading unrelated configuration files in our unit
* tests.
*/
@VisibleForTesting
/* package */ static Optional readAggregatedConfigurationForFolder(
File initialFolder, @Nullable String boundaryFolderName) throws IOException {
TeamscaleIntegrationConfiguration aggregatedConfiguration = null;
File currentFolder = initialFolder;
while (currentFolder != null && currentFolder.exists()) {
if (currentFolder.getName().equals(boundaryFolderName)) {
// we must not explore any further up
break;
}
File potentialTomlFile = new File(currentFolder, CONFIGURATION_FILE_NAME);
// will try in parent directory in next loop iteration
currentFolder = currentFolder.getParentFile();
if (!potentialTomlFile.canRead()) {
// file does not exist or we are not allowed to read it
continue;
}
TeamscaleIntegrationConfiguration parentTomlFile = readSingleConfigurationFile(potentialTomlFile);
if (aggregatedConfiguration == null) {
aggregatedConfiguration = parentTomlFile;
} else {
// if some config items are not set in the current aggregated configuration,
// then we try to import these items from the parentTomlFile
aggregatedConfiguration = aggregatedConfiguration
.cloneAndImportMissingConfigurationItemsFrom(parentTomlFile);
}
if (aggregatedConfiguration != null && aggregatedConfiguration.isRootConfiguration) {
// early abort if we read a file with the "isRoot" property set to true
break;
}
}
if (aggregatedConfiguration != null) {
aggregatedConfiguration.validate(initialFolder);
}
return Optional.ofNullable(aggregatedConfiguration);
}
private static TeamscaleIntegrationConfiguration readSingleConfigurationFile(File tomlFile) throws IOException {
TomlMapper mapper = TomlMapper.builder().build();
try {
Optional versionNumber = readFileFormatVersionNumber(tomlFile, mapper);
if (!versionNumber.isPresent() || versionNumber.get().equals("1.0")) {
// if no version number is specified, the specification says version 1.0 is used
// implicitly
TeamscaleIntegrationConfigurationFileV1 versionedFile = mapper.readValue(tomlFile,
TeamscaleIntegrationConfigurationFileV1.class);
return versionedFile.convertToInternalFormat(tomlFile);
} else {
throw new IOException("Could not parse Teamscale Integration TOML file " + tomlFile.getAbsolutePath()
+ ". File format version number " + versionNumber.get() + " not known.");
}
} catch (UnrecognizedPropertyException | TomlStreamReadException e) {
throw new IOException("Could not parse Teamscale Integration TOML file " + tomlFile.getAbsolutePath() + ".",
e);
}
}
/**
* Reads the version number of the toml file (if any is given). This must be
* done before deserialization because we need to choose the deserialization
* class based on the format version.
*/
private static Optional readFileFormatVersionNumber(File tomlFile, TomlMapper mapper) throws IOException {
JsonNode tree = mapper.readTree(tomlFile);
JsonNode versionNode = tree.findValue(FORMAT_VERSION_PROPERTY_NAME);
if (versionNode == null || versionNode.isMissingNode() || !versionNode.isTextual()) {
return Optional.empty();
}
return Optional.of(versionNode.asText());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy